diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 2f50f27c25..68533e416a 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -43,6 +43,7 @@ swift_library( "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/Components/SheetComponent", + "//submodules/Components/BundleIconComponent", "//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/TelegramNotices", diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 6b3f1f5062..e95b6709e4 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -47,6 +47,7 @@ private final class ChannelStatsControllerArguments { let requestTonWithdraw: () -> Void let requestStarsWithdraw: () -> Void + let showTimeoutTooltip: (Int32) -> Void let openMonetizationIntro: () -> Void let openMonetizationInfo: () -> Void let openTonTransaction: (RevenueStatsTransactionsContext.State.Transaction) -> Void @@ -56,7 +57,7 @@ private final class ChannelStatsControllerArguments { let presentCpmLocked: () -> Void let dismissInput: () -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPostStats: @escaping (EnginePeer, StatsPostItem) -> Void, openStory: @escaping (EngineStoryItem, UIView) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void, updateStarsSelected: @escaping (Bool) -> Void, requestTonWithdraw: @escaping () -> Void, requestStarsWithdraw: @escaping () -> Void, openMonetizationIntro: @escaping () -> Void, openMonetizationInfo: @escaping () -> Void, openTonTransaction: @escaping (RevenueStatsTransactionsContext.State.Transaction) -> Void, openStarsTransaction: @escaping (StarsContext.State.Transaction) -> Void, expandTransactions: @escaping () -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPostStats: @escaping (EnginePeer, StatsPostItem) -> Void, openStory: @escaping (EngineStoryItem, UIView) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void, updateStarsSelected: @escaping (Bool) -> Void, requestTonWithdraw: @escaping () -> Void, requestStarsWithdraw: @escaping () -> Void, showTimeoutTooltip: @escaping (Int32) -> Void, openMonetizationIntro: @escaping () -> Void, openMonetizationInfo: @escaping () -> Void, openTonTransaction: @escaping (RevenueStatsTransactionsContext.State.Transaction) -> Void, openStarsTransaction: @escaping (StarsContext.State.Transaction) -> Void, expandTransactions: @escaping () -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, dismissInput: @escaping () -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openPostStats = openPostStats @@ -72,6 +73,7 @@ private final class ChannelStatsControllerArguments { self.updateStarsSelected = updateStarsSelected self.requestTonWithdraw = requestTonWithdraw self.requestStarsWithdraw = requestStarsWithdraw + self.showTimeoutTooltip = showTimeoutTooltip self.openMonetizationIntro = openMonetizationIntro self.openMonetizationInfo = openMonetizationInfo self.openTonTransaction = openTonTransaction @@ -241,7 +243,7 @@ private enum StatsEntry: ItemListNodeEntry { case adsTonBalanceInfo(PresentationTheme, String) case adsStarsBalanceTitle(PresentationTheme, String) - case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool) + case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Int32?) case adsStarsBalanceInfo(PresentationTheme, String) case adsTransactionsTitle(PresentationTheme, String) @@ -805,8 +807,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .adsStarsBalance(lhsTheme, lhsStats, lhsCanWithdraw, lhsIsEnabled): - if case let .adsStarsBalance(rhsTheme, rhsStats, rhsCanWithdraw, rhsIsEnabled) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsCanWithdraw == rhsCanWithdraw, lhsIsEnabled == rhsIsEnabled { + case let .adsStarsBalance(lhsTheme, lhsStats, lhsCanWithdraw, lhsIsEnabled, lhsCooldownUntilTimestamp): + if case let .adsStarsBalance(rhsTheme, rhsStats, rhsCanWithdraw, rhsIsEnabled, rhsCooldownUntilTimestamp) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsCanWithdraw == rhsCanWithdraw, lhsIsEnabled == rhsIsEnabled, lhsCooldownUntilTimestamp == rhsCooldownUntilTimestamp { return true } else { return false @@ -1050,9 +1052,11 @@ private enum StatsEntry: ItemListNodeEntry { stats: stats, canWithdraw: canWithdraw, isEnabled: isEnabled, + actionCooldownUntilTimestamp: nil, withdrawAction: { arguments.requestTonWithdraw() }, + buyAdsAction: nil, sectionId: self.section, style: .blocks ) @@ -1060,16 +1064,30 @@ private enum StatsEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in arguments.openMonetizationInfo() }) - case let .adsStarsBalance(_, stats, canWithdraw, isEnabled): + case let .adsStarsBalance(_, stats, canWithdraw, isEnabled, cooldownUntilTimestamp): return MonetizationBalanceItem( context: arguments.context, presentationData: presentationData, stats: stats, canWithdraw: canWithdraw, isEnabled: isEnabled, + actionCooldownUntilTimestamp: cooldownUntilTimestamp, withdrawAction: { - arguments.requestStarsWithdraw() + var remainingCooldownSeconds: Int32 = 0 + if let cooldownUntilTimestamp { + remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + remainingCooldownSeconds = max(0, remainingCooldownSeconds) + + if remainingCooldownSeconds > 0 { + arguments.showTimeoutTooltip(cooldownUntilTimestamp) + } else { + arguments.requestStarsWithdraw() + } + } else { + arguments.requestStarsWithdraw() + } }, + buyAdsAction: nil, sectionId: self.section, style: .blocks ) @@ -1538,7 +1556,7 @@ private func monetizationEntries( if let starsData, starsData.balances.overallRevenue > 0 { entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle)) - entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > 0, starsData.balances.withdrawEnabled)) + entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > 0, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp)) entries.append(.adsStarsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_Balance_StarsInfo)) } @@ -1780,6 +1798,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var openStarsTransactionImpl: ((StarsContext.State.Transaction) -> Void)? var requestTonWithdrawImpl: (() -> Void)? var requestStarsWithdrawImpl: (() -> Void)? + var showTimeoutTooltipImpl: ((Int32) -> Void)? var updateStatusBarImpl: ((StatusBarStyle) -> Void)? var dismissInputImpl: (() -> Void)? @@ -1911,6 +1930,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD requestStarsWithdraw: { requestStarsWithdrawImpl?() }, + showTimeoutTooltip: { timestamp in + showTimeoutTooltipImpl?(timestamp) + }, openMonetizationIntro: { let controller = MonetizationIntroScreen(context: context, openMore: {}) pushImpl?(controller) @@ -2374,6 +2396,52 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } })) } + var tooltipScreen: UndoOverlayController? + var timer: Foundation.Timer? + showTimeoutTooltipImpl = { cooldownUntilTimestamp in + let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let content: UndoOverlayContent = .universal( + animation: "anim_clock", + scale: 0.058, + colors: [:], + title: nil, + text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string, + customUndoText: nil, + timeout: nil + ) + let controller = UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in + return true + }) + tooltipScreen = controller + presentImpl?(controller) + + if remainingCooldownSeconds < 3600 { + if timer == nil { + timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in + if let tooltipScreen { + let remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + let content: UndoOverlayContent = .universal( + animation: "anim_clock", + scale: 0.058, + colors: [:], + title: nil, + text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorTimeout(stringForRemainingTime(remainingCooldownSeconds)).string, + customUndoText: nil, + timeout: nil + ) + tooltipScreen.content = content + } else { + if let currentTimer = timer { + timer = nil + currentTimer.invalidate() + } + } + }) + } + } + } openTonTransactionImpl = { transaction in let _ = (peer.get() |> take(1) diff --git a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift index b34980807d..591754a6eb 100644 --- a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift +++ b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift @@ -9,6 +9,9 @@ import ItemListUI import SolidRoundedButtonNode import TelegramCore import TextFormat +import ComponentFlow +import ButtonComponent +import BundleIconComponent final class MonetizationBalanceItem: ListViewItem, ItemListItem { let context: AccountContext @@ -16,7 +19,9 @@ final class MonetizationBalanceItem: ListViewItem, ItemListItem { let stats: Stats let canWithdraw: Bool let isEnabled: Bool + let actionCooldownUntilTimestamp: Int32? let withdrawAction: () -> Void + let buyAdsAction: (() -> Void)? let sectionId: ItemListSectionId let style: ItemListStyle @@ -26,7 +31,9 @@ final class MonetizationBalanceItem: ListViewItem, ItemListItem { stats: Stats, canWithdraw: Bool, isEnabled: Bool, + actionCooldownUntilTimestamp: Int32?, withdrawAction: @escaping () -> Void, + buyAdsAction: (() -> Void)?, sectionId: ItemListSectionId, style: ItemListStyle ) { @@ -35,7 +42,9 @@ final class MonetizationBalanceItem: ListViewItem, ItemListItem { self.stats = stats self.canWithdraw = canWithdraw self.isEnabled = isEnabled + self.actionCooldownUntilTimestamp = actionCooldownUntilTimestamp self.withdrawAction = withdrawAction + self.buyAdsAction = buyAdsAction self.sectionId = sectionId self.style = style } @@ -85,12 +94,14 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { private let iconNode: ASImageNode private let balanceTextNode: TextNode private let valueTextNode: TextNode - - private var withdrawButtonNode: SolidRoundedButtonNode? + private var button = ComponentView() private let activateArea: AccessibilityAreaNode + private var timer: Foundation.Timer? + private var item: MonetizationBalanceItem? + private var buttonLayout: (isStars: Bool, origin: CGFloat, width: CGFloat, leftInset: CGFloat, rightInset: CGFloat)? override var canBeSelected: Bool { return false @@ -303,37 +314,91 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { strongSelf.valueTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - valueLayout.size.width) / 2.0), y: balanceTextFrame.maxY - 5.0), size: valueLayout.size) - if item.canWithdraw { - let withdrawButtonNode: SolidRoundedButtonNode - if let currentWithdrawButtonNode = strongSelf.withdrawButtonNode { - withdrawButtonNode = currentWithdrawButtonNode - } else { - var buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - buttonTheme = buttonTheme.withUpdated(disabledBackgroundColor: buttonTheme.backgroundColor, disabledForegroundColor: buttonTheme.foregroundColor.withAlphaComponent(0.6)) - withdrawButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: buttonHeight, cornerRadius: 11.0) - withdrawButtonNode.pressed = { [weak self] in - if let self, let item = self.item, item.isEnabled { - item.withdrawAction() - } - } - strongSelf.addSubnode(withdrawButtonNode) - strongSelf.withdrawButtonNode = withdrawButtonNode - } - withdrawButtonNode.title = isStars ? item.presentationData.strings.Monetization_BalanceStarsWithdraw : item.presentationData.strings.Monetization_BalanceWithdraw - withdrawButtonNode.isEnabled = item.isEnabled - - let buttonWidth = contentSize.width - leftInset - rightInset - let _ = withdrawButtonNode.updateLayout(width: buttonWidth, transition: .immediate) - withdrawButtonNode.frame = CGRect(x: leftInset, y: strongSelf.valueTextNode.frame.maxY + buttonSpacing + 3.0, width: buttonWidth, height: buttonHeight) - } else { - strongSelf.withdrawButtonNode?.removeFromSupernode() - strongSelf.withdrawButtonNode = nil - } + strongSelf.buttonLayout = (isStars: isStars, origin: strongSelf.valueTextNode.frame.maxY + buttonSpacing + 3.0, width: params.width, leftInset: leftInset, rightInset: rightInset) + strongSelf.updateButton() } }) } } + func updateButton() { + guard let item = self.item, let (isStars, origin, width, leftInset, rightInset) = self.buttonLayout else { + return + } + + if item.canWithdraw { + var remainingCooldownSeconds: Int32 = 0 + if let cooldownUntilTimestamp = item.actionCooldownUntilTimestamp { + remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970) + remainingCooldownSeconds = max(0, remainingCooldownSeconds) + } + + if remainingCooldownSeconds > 0 { + if self.timer == nil { + self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + self.updateButton() + }) + } + } else { + if let timer = self.timer { + self.timer = nil + timer.invalidate() + } + } + + let actionTitle = isStars ? item.presentationData.strings.Monetization_BalanceStarsWithdraw : item.presentationData.strings.Monetization_BalanceWithdraw + let content: AnyComponentWithIdentity + if remainingCooldownSeconds > 0 { + content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent( + VStack([ + AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: item.presentationData.theme.list.itemCheckColors.foregroundColor))), + AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(HStack([ + AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Chat List/StatusLockIcon", tintColor: item.presentationData.theme.list.itemCheckColors.fillColor.mixedWith(item.presentationData.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))), + AnyComponentWithIdentity(id: 0, component: AnyComponent(Text(text: stringForRemainingTime(remainingCooldownSeconds), font: Font.with(size: 11.0, weight: .medium, traits: [.monospacedNumbers]), color: item.presentationData.theme.list.itemCheckColors.fillColor.mixedWith(item.presentationData.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))) + ], spacing: 3.0))) + ], spacing: 1.0) + )) + } else { + content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: item.presentationData.theme.list.itemCheckColors.foregroundColor))) + } + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: item.presentationData.theme.list.itemCheckColors.fillColor, + foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: content, + isEnabled: item.isEnabled, + allowActionWhenDisabled: false, + displaysProgress: false, + action: { [weak self] in + guard let self, let item = self.item, item.isEnabled else { + return + } + item.withdrawAction() + } + )), + environment: {}, + containerSize: CGSize(width: width - leftInset - rightInset, height: 50.0) + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + let buttonFrame = CGRect(origin: CGPoint(x: leftInset, y: origin), size: buttonSize) + buttonView.frame = buttonFrame + } + } else if let buttonView = self.button.view { + buttonView.removeFromSuperview() + } + } + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) } @@ -346,3 +411,16 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } } + +func stringForRemainingTime(_ duration: Int32) -> String { + let hours = duration / 3600 + let minutes = duration / 60 % 60 + let seconds = duration % 60 + let durationString: String + if hours > 0 { + durationString = String(format: "%d:%02d", hours, minutes) + } else { + durationString = String(format: "%02d:%02d", minutes, seconds) + } + return durationString +} diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift index fc5918dbae..f148dc67b6 100644 --- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift +++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift @@ -316,7 +316,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode { theme: item.presentationData.theme, title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0), - leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: item.transaction.peer, photo: item.transaction.photo, media: item.transaction.media))), false), + leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: item.transaction.peer, photo: nil, media: [], backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false), icon: nil, accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index c8f163f360..5bba89ca47 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -18,13 +18,15 @@ public final class StarsAvatarComponent: Component { let peer: StarsContext.State.Transaction.Peer let photo: TelegramMediaWebFile? let media: [Media] + let backgroundColor: UIColor - public init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?, media: [Media]) { + public init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?, media: [Media], backgroundColor: UIColor) { self.context = context self.theme = theme self.peer = peer self.photo = photo self.media = media + self.backgroundColor = backgroundColor } public static func ==(lhs: StarsAvatarComponent, rhs: StarsAvatarComponent) -> Bool { @@ -43,6 +45,9 @@ public final class StarsAvatarComponent: Component { if !areMediaArraysEqual(lhs.media, rhs.media) { return false } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } return true } @@ -51,6 +56,8 @@ public final class StarsAvatarComponent: Component { private let backgroundView = UIImageView() private let iconView = UIImageView() private var imageNode: TransformImageNode? + private var imageFrameNode: UIView? + private var secondImageNode: TransformImageNode? private let fetchDisposable = MetaDisposable() @@ -112,10 +119,49 @@ public final class StarsAvatarComponent: Component { imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), autoFetchFullSizeThumbnail: true)) } } - - imageNode.frame = CGRect(origin: .zero, size: size) + + var imageFrame = CGRect(origin: .zero, size: size) + if component.media.count > 1 { + imageFrame = imageFrame.insetBy(dx: 2.0, dy: 2.0).offsetBy(dx: -2.0, dy: 2.0) + } + imageNode.frame = imageFrame imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: dimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + if component.media.count > 1 { + let secondImageNode: TransformImageNode + let imageFrameNode: UIView + if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { + secondImageNode = current + imageFrameNode = currentFrame + } else { + secondImageNode = TransformImageNode() + secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] + self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) + self.secondImageNode = secondImageNode + + imageFrameNode = UIView() + imageFrameNode.layer.cornerRadius = 8.0 + self.insertSubview(imageFrameNode, belowSubview: imageNode.view) + self.imageFrameNode = imageFrameNode + + if let image = component.media[1] as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + dimensions = imageDimensions.cgSize.aspectFilled(size) + } + secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) + } else if let file = component.media[1] as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + dimensions = videoDimensions.cgSize.aspectFilled(size) + } + secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) + } + } + imageFrameNode.backgroundColor = component.backgroundColor + secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: dimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + secondImageNode.frame = imageFrame.offsetBy(dx: 4.0, dy: -4.0) + imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel) + } + self.backgroundView.isHidden = true self.iconView.isHidden = true self.avatarNode.isHidden = true diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 4587ef8059..1fedd06bc3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -458,6 +458,11 @@ public final class StarsImageComponent: Component { dimensions = imageDimensions.cgSize.aspectFilled(imageSize) } secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) + } else if let file = media[1] as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + dimensions = videoDimensions.cgSize.aspectFilled(imageSize) + } + secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) } } imageFrameNode.backgroundColor = component.backgroundColor diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 49ef5fdbe1..85056b50d2 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -299,7 +299,7 @@ final class StarsTransactionsListPanelComponent: Component { theme: environment.theme, title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right), - leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: item.peer, photo: item.photo, media: item.media))), false), + leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: item.peer, photo: item.photo, media: item.media, backgroundColor: environment.theme.list.plainBackgroundColor))), false), icon: nil, accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), action: { [weak self] _ in