diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 805a41a728..7c8136828f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5633,6 +5633,8 @@ Sorry for the inconvenience."; "Settings.RemoveConfirmation" = "Remove"; +"Conversation.ContextMenuOpenInfo" = "Open Info"; + "Conversation.ContextMenuOpenProfile" = "Open Profile"; "Conversation.ContextMenuSendMessage" = "Send Message"; "Conversation.ContextMenuMention" = "Mention"; @@ -12883,3 +12885,22 @@ Sorry for the inconvenience."; "WebBrowser.ShowInstantView" = "Show Instant View"; "WebBrowser.HideInstantView" = "Hide Instant View"; + +"Stats.Boosts.Stars_1" = "%@ Star"; +"Stats.Boosts.Stars_any" = "%@ Stars"; +"Stats.Boosts.StarsBoosts" = "12-month boosts"; + +"BoostGift.PrepaidGiveaway.StarsCount_1" = "%@ Star"; +"BoostGift.PrepaidGiveaway.StarsCount_any" = "%@ Stars"; +"BoostGift.PrepaidGiveaway.StarsBoosts" = "12-month boosts"; + +"Stars.Transaction.Giveaway.Boost.Stars_1" = "%@ Star"; +"Stars.Transaction.Giveaway.Boost.Stars_any" = "%@ Stars"; +"Stars.Transaction.Giveaway.Boost.Boosts_1" = "%@ Boost"; +"Stars.Transaction.Giveaway.Boost.Boosts_any" = "%@ Boosts"; +"Stars.Transaction.Giveaway.Boost.Members_1" = "%@ Member"; +"Stars.Transaction.Giveaway.Boost.Members_any" = "%@ Members"; +"Stars.Transaction.Giveaway.Boost.Subscriber_1" = "%@ Subscriber"; +"Stars.Transaction.Giveaway.Boost.Subscriber_any" = "%@ Subscribers"; + +"Conversation.ContextMenuCopyEmail" = "Copy Email"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0168008557..8a704ace67 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1016,6 +1016,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController + func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index b50acdf9ef..44343f5102 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -459,6 +459,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .prepaid(_, title, subtitle, prepaidGiveaway): let color: GiftOptionItem.Icon.Color + let icon: String + let boosts: Int32 switch prepaidGiveaway.prize { case let .premium(months): switch months { @@ -471,16 +473,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { default: color = .blue } - case let .stars(amount, _): - if amount <= 1000 { - color = .green - } else if amount < 2500 { - color = .blue - } else { - color = .red - } + icon = "Premium/Giveaway" + boosts = prepaidGiveaway.quantity * 4 + case let .stars(_, boostCount): + color = .stars + icon = "Premium/PremiumStar" + boosts = boostCount } - return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, sectionId: self.section, action: nil) + return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: icon), title: title, titleFont: .bold, titleBadge: "\(boosts)", subtitle: subtitle, sectionId: self.section, action: nil) case let .starsHeader(_, text, additionalText): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section) case let .stars(_, _, stars, title, subtitle, label, isSelected, maxWinners): @@ -778,9 +778,8 @@ private func createGiveawayControllerEntries( title = presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity) text = presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(months)").string case let .stars(stars, _): - //TODO:localize - title = "\(stars) Telegram Stars" - text = "among \(prepaidGiveaway.quantity) winners" + title = presentationData.strings.BoostGift_PrepaidGiveaway_StarsCount(Int32(stars)) + text = presentationData.strings.BoostGift_PrepaidGiveaway_StarsBoosts } entries.append(.prepaid(presentationData.theme, title, text, prepaidGiveaway)) } diff --git a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift index db44a0c5ea..f65c452521 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift @@ -223,7 +223,9 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode { isVisible: true, hasIdleAnimations: true, colors: colors, - particleColor: particleColor + particleColor: particleColor, + backgroundColor: self.item.theme.list.blocksBackgroundColor + )) let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0) diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift index 33aad00749..8eb974d142 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift @@ -614,6 +614,7 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent { )), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, + clipsContent: true, animateOut: animateOut ), environment: { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 56c06c7e88..25ebe778d4 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -682,7 +682,8 @@ private func privacyAndSecurityControllerEntries( } else { value = privacySettings.accountRemovalTimeout } - entries.append(.accountTimeout(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountIfAwayFor, timeIntervalString(strings: presentationData.strings, value: value))) + + entries.append(.accountTimeout(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountIfAwayFor, presentationData.strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))))) } else { entries.append(.accountTimeout(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountIfAwayFor, presentationData.strings.Channel_NotificationLoading)) } @@ -1174,10 +1175,11 @@ public func privacyAndSecurityController( 1 * 30 * 24 * 60 * 60, 3 * 30 * 24 * 60 * 60, 6 * 30 * 24 * 60 * 60, - 365 * 24 * 60 * 60 + 365 * 24 * 60 * 60, + 548 * 24 * 60 * 60 ] var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in - return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value), action: { + return ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))), action: { dismissAction() timeoutAction(value) }) diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 830e32543f..bc9d8bbd65 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -962,7 +962,7 @@ private enum StatsEntry: ItemListNodeEntry { case let .booster(_, _, _, boost): let count = boost.multiplier let expiresValue = stringForDate(timestamp: boost.expires, strings: presentationData.strings) - let expiresString: String + var expiresString: String let durationMonths = Int32(round(Float(boost.expires - boost.date) / (86400.0 * 30.0))) let durationString = presentationData.strings.Stats_Boosts_ShortMonth("\(durationMonths)").string @@ -998,17 +998,23 @@ private enum StatsEntry: ItemListNodeEntry { expiresString = presentationData.strings.Stats_Boosts_ExpiresOn(expiresValue).string } } else { + expiresString = "\(durationString) • \(expiresValue)" if boost.flags.contains(.isUnclaimed) { title = presentationData.strings.Stats_Boosts_Unclaimed icon = .image(color: color, name: "Premium/Unclaimed") } else if boost.flags.contains(.isGiveaway) { - title = presentationData.strings.Stats_Boosts_ToBeDistributed - icon = .image(color: color, name: "Premium/ToBeDistributed") + if let stars = boost.stars { + title = presentationData.strings.Stats_Boosts_Stars(Int32(stars)) + icon = .image(color: .stars, name: "Premium/PremiumStar") + expiresString = expiresValue + } else { + title = presentationData.strings.Stats_Boosts_ToBeDistributed + icon = .image(color: color, name: "Premium/ToBeDistributed") + } } else { title = "Unknown" icon = .image(color: color, name: "Premium/ToBeDistributed") } - expiresString = "\(durationString) • \(expiresValue)" } return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: icon, title: title, titleFont: .bold, titleBadge: count > 1 ? "\(count)" : nil, subtitle: expiresString, label: label.flatMap { .semitransparent($0) }, sectionId: self.section, action: { arguments.openBoost(boost) @@ -1038,6 +1044,8 @@ private enum StatsEntry: ItemListNodeEntry { }) case let .boostPrepaid(_, _, title, subtitle, prepaidGiveaway): let color: GiftOptionItem.Icon.Color + let icon: String + var boosts: Int32 switch prepaidGiveaway.prize { case let .premium(months): switch months { @@ -1050,16 +1058,14 @@ private enum StatsEntry: ItemListNodeEntry { default: color = .blue } - case let .stars(amount, _): - if amount <= 1000 { - color = .green - } else if amount < 2500 { - color = .blue - } else { - color = .red - } + icon = "Premium/Giveaway" + boosts = prepaidGiveaway.quantity * 4 + case let .stars(_, boostCount): + color = .stars + icon = "Premium/PremiumStar" + boosts = boostCount } - return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, label: nil, sectionId: self.section, action: { + return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: icon), title: title, titleFont: .bold, titleBadge: "\(boosts)", subtitle: subtitle, label: nil, sectionId: self.section, action: { arguments.createPrepaidGiveaway(prepaidGiveaway) }) case let .adsHeader(_, text): @@ -1441,8 +1447,8 @@ private func boostsEntries( title = presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity) text = presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(months)").string case let .stars(stars, _): - title = "\(stars) Telegram Stars" - text = "among \(giveaway.quantity) winners" + title = presentationData.strings.Stats_Boosts_Stars(Int32(stars)) + text = presentationData.strings.Stats_Boosts_StarsBoosts } entries.append(.boostPrepaid(i, presentationData.theme, title, text, giveaway)) i += 1 @@ -1942,8 +1948,13 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } if boost.peer == nil, boost.flags.contains(.isGiveaway) && !boost.flags.contains(.isUnclaimed) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Stats_Boosts_TooltipToBeDistributed, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })) + if let _ = boost.stars { + let controller = context.sharedContext.makeStarsGiveawayBoostScreen(context: context, peerId: peerId, boost: boost) + pushImpl?(controller) + } else { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Stats_Boosts_TooltipToBeDistributed, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })) + } return } diff --git a/submodules/TelegramCore/Sources/State/ChannelBoost.swift b/submodules/TelegramCore/Sources/State/ChannelBoost.swift index afdde65658..6284735408 100644 --- a/submodules/TelegramCore/Sources/State/ChannelBoost.swift +++ b/submodules/TelegramCore/Sources/State/ChannelBoost.swift @@ -200,7 +200,7 @@ private final class ChannelBoostersContextImpl { var result: [ChannelBoostersContext.State.Boost] = [] for boost in cachedResult.boosts { let peer = boost.peerId.flatMap { transaction.getPeer($0) } - result.append(ChannelBoostersContext.State.Boost(flags: ChannelBoostersContext.State.Boost.Flags(rawValue: boost.flags), id: boost.id, peer: peer.flatMap { EnginePeer($0) }, date: boost.date, expires: boost.expires, multiplier: boost.multiplier, slug: boost.slug)) + result.append(ChannelBoostersContext.State.Boost(flags: ChannelBoostersContext.State.Boost.Flags(rawValue: boost.flags), id: boost.id, peer: peer.flatMap { EnginePeer($0) }, date: boost.date, expires: boost.expires, multiplier: boost.multiplier, stars: boost.stars, slug: boost.slug, giveawayMessageId: boost.giveawayMessageId)) } return (result, cachedResult.count, true) } else { @@ -452,6 +452,7 @@ private final class CachedChannelBoosters: Codable { case date case expires case multiplier + case stars case slug case channelPeerId case giveawayMessageId @@ -463,17 +464,19 @@ private final class CachedChannelBoosters: Codable { var date: Int32 var expires: Int32 var multiplier: Int32 + var stars: Int64? var slug: String? var channelPeerId: EnginePeer.Id var giveawayMessageId: EngineMessage.Id? - init(flags: Int32, id: String, peerId: EnginePeer.Id?, date: Int32, expires: Int32, multiplier: Int32, slug: String?, channelPeerId: EnginePeer.Id, giveawayMessageId: EngineMessage.Id?) { + init(flags: Int32, id: String, peerId: EnginePeer.Id?, date: Int32, expires: Int32, multiplier: Int32, stars: Int64?, slug: String?, channelPeerId: EnginePeer.Id, giveawayMessageId: EngineMessage.Id?) { self.flags = flags self.id = id self.peerId = peerId self.date = date self.expires = expires self.multiplier = multiplier + self.stars = stars self.slug = slug self.channelPeerId = channelPeerId self.giveawayMessageId = giveawayMessageId @@ -488,6 +491,7 @@ private final class CachedChannelBoosters: Codable { self.date = try container.decode(Int32.self, forKey: .date) self.expires = try container.decode(Int32.self, forKey: .expires) self.multiplier = try container.decode(Int32.self, forKey: .multiplier) + self.stars = try container.decodeIfPresent(Int64.self, forKey: .stars) self.slug = try container.decodeIfPresent(String.self, forKey: .slug) self.channelPeerId = EnginePeer.Id(try container.decode(Int64.self, forKey: .channelPeerId)) self.giveawayMessageId = try container.decodeIfPresent(Int32.self, forKey: .giveawayMessageId).flatMap { EngineMessage.Id(peerId: self.channelPeerId, namespace: Namespaces.Message.Cloud, id: $0) } @@ -502,6 +506,7 @@ private final class CachedChannelBoosters: Codable { try container.encode(self.date, forKey: .date) try container.encode(self.expires, forKey: .expires) try container.encode(self.multiplier, forKey: .multiplier) + try container.encodeIfPresent(self.stars, forKey: .stars) try container.encodeIfPresent(self.slug, forKey: .slug) try container.encode(self.channelPeerId.toInt64(), forKey: .channelPeerId) try container.encodeIfPresent(self.giveawayMessageId?.id, forKey: .giveawayMessageId) @@ -518,7 +523,7 @@ private final class CachedChannelBoosters: Codable { } init(channelPeerId: EnginePeer.Id, boosts: [ChannelBoostersContext.State.Boost], count: Int32) { - self.boosts = boosts.map { CachedBoost(flags: $0.flags.rawValue, id: $0.id, peerId: $0.peer?.id, date: $0.date, expires: $0.expires, multiplier: $0.multiplier, slug: $0.slug, channelPeerId: channelPeerId, giveawayMessageId: $0.giveawayMessageId) } + self.boosts = boosts.map { CachedBoost(flags: $0.flags.rawValue, id: $0.id, peerId: $0.peer?.id, date: $0.date, expires: $0.expires, multiplier: $0.multiplier, stars: $0.stars, slug: $0.slug, channelPeerId: channelPeerId, giveawayMessageId: $0.giveawayMessageId) } self.count = count } diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift index 3ec7358d8b..8a531f9c82 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift @@ -76,6 +76,7 @@ public final class PremiumStarComponent: Component { let hasIdleAnimations: Bool let colors: [UIColor]? let particleColor: UIColor? + let backgroundColor: UIColor? public init( theme: PresentationTheme, @@ -83,7 +84,8 @@ public final class PremiumStarComponent: Component { isVisible: Bool, hasIdleAnimations: Bool, colors: [UIColor]? = nil, - particleColor: UIColor? = nil + particleColor: UIColor? = nil, + backgroundColor: UIColor? = nil ) { self.theme = theme self.isIntro = isIntro @@ -91,10 +93,11 @@ public final class PremiumStarComponent: Component { self.hasIdleAnimations = hasIdleAnimations self.colors = colors self.particleColor = particleColor + self.backgroundColor = backgroundColor } public static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool { - return lhs.theme === rhs.theme && lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors && lhs.particleColor == rhs.particleColor + return lhs.theme === rhs.theme && lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors && lhs.particleColor == rhs.particleColor && lhs.backgroundColor == rhs.backgroundColor } public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { @@ -694,8 +697,10 @@ public final class PremiumStarComponent: Component { self.playAppearanceAnimation(velocity: nil, mirror: component.colors?.contains(UIColor(rgb: 0xe57d02)) == true, explode: true, force: true) } - if let _ = component.particleColor { - self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor + if let backgroundColor = component.backgroundColor { + self.sceneView.backgroundColor = backgroundColor + } else { + self.sceneView.backgroundColor = .clear } self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 72e92b950f..09afb0a4e3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -707,7 +707,11 @@ public final class StarsImageComponent: Component { } else { avatarNode = ImageNode() avatarNode.displaysAsynchronously = false - containerNode.view.addSubview(avatarNode.view) + if let smallIconOutlineView = self.smallIconOutlineView { + containerNode.view.insertSubview(avatarNode.view, belowSubview: smallIconOutlineView) + } else { + containerNode.view.addSubview(avatarNode.view) + } self.avatarNode = avatarNode avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true)) diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 4e44fa7a8d..5493510405 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -786,7 +786,8 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { UIColor(rgb: 0xf9b004), UIColor(rgb: 0xfdd219) ], - particleColor: UIColor(rgb: 0xf9b004) + particleColor: UIColor(rgb: 0xf9b004), + backgroundColor: environment.theme.list.blocksBackgroundColor ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD index 62a8ef68a8..b814269b7d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD @@ -35,6 +35,7 @@ swift_library( "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", "//submodules/GalleryUI", "//submodules/TelegramUI/Components/MiniAppListScreen", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index 3af792b6ac..4e8ba3c566 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -24,6 +24,7 @@ import StarsImageComponent import GalleryUI import StarsAvatarComponent import MiniAppListScreen +import PremiumStarComponent private final class StarsTransactionSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -104,6 +105,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { peerIds.append(subscription.peer.id) case let .importer(_, _, importer, _): peerIds.append(importer.peer.peerId) + case let .boost(peerId, _): + peerIds.append(peerId) } self.disposable = (context.engine.data.get( @@ -141,6 +144,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { let closeButton = Child(Button.self) let title = Child(MultilineTextComponent.self) let star = Child(StarsImageComponent.self) + let activeStar = Child(PremiumStarComponent.self) + let amountBackground = Child(RoundedRectangle.self) let amount = Child(BalancedTextComponent.self) let amountStar = Child(BundleIconComponent.self) let description = Child(MultilineTextComponent.self) @@ -191,6 +196,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let titleText: String let amountText: String var descriptionText: String + var boostsText: String? let additionalText = strings.Stars_Transaction_Terms var buttonText: String? = strings.Common_OK var buttonIsDestructive = false @@ -206,6 +212,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { var via: String? var messageId: EngineMessage.Id? var toPeer: EnginePeer? +// var toString: String? var transactionPeer: StarsContext.State.Transaction.Peer? var media: [AnyMediaReference] = [] var photo: TelegramMediaWebFile? @@ -217,9 +224,24 @@ private final class StarsTransactionSheetContent: CombinedComponent { var isCancelled = false var isReaction = false var giveawayMessageId: MessageId? + var isBoost = false var delayedCloseOnOpenPeer = true switch subject { + case let .boost(peerId, boost): + guard let stars = boost.stars else { + fatalError() + } + let boosts = boost.multiplier + titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars)) + descriptionText = "" + boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts) + count = stars + date = boost.date + toPeer = state.peerMap[peerId] +// toString = strings.Stars_Transaction_Giveaway_Boost_Subscribers(boost.quantity) + giveawayMessageId = boost.giveawayMessageId + isBoost = true case let .importer(peer, pricing, importer, usdRate): let usdValue = formatTonUsdValue(pricing.amount, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat) titleText = strings.Stars_Transaction_Subscription_Title @@ -493,7 +515,14 @@ private final class StarsTransactionSheetContent: CombinedComponent { let formattedAmount = presentationStringsFormattedNumber(abs(Int32(count)), dateTimeFormat.groupingSeparator) let countColor: UIColor - if isSubscription || isSubscriber { + var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0) + var countBackgroundColor: UIColor? + if let boostsText { + amountText = boostsText + countColor = .white + countBackgroundColor = UIColor(rgb: 0x9671ff) + countFont = Font.with(size: 14.0, design: .round, weight: .semibold) + } else if isSubscription || isSubscriber { amountText = strings.Stars_Transaction_Subscription_PerMonth(formattedAmount).string countColor = theme.list.itemSecondaryTextColor } else if countIsGeneric { @@ -542,23 +571,45 @@ private final class StarsTransactionSheetContent: CombinedComponent { } else { imageIcon = nil } - let star = star.update( - component: StarsImageComponent( - context: component.context, - subject: imageSubject, - theme: theme, - diameter: 90.0, - backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, - icon: imageIcon, - action: !media.isEmpty ? { transitionNode, addToTransitionSurface in - component.openMedia(media.map { $0.media }, transitionNode, addToTransitionSurface) - } : nil - ), - availableSize: CGSize(width: context.availableSize.width, height: 200.0), - transition: .immediate - ) - - let amountAttributedText = NSMutableAttributedString(string: amountText, font: isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0), textColor: countColor) + var starChild: _UpdatedChildComponent + if isBoost { + starChild = activeStar.update( + component: PremiumStarComponent( + theme: theme, + isIntro: false, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0xe57d02), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xf9b004), + UIColor(rgb: 0xfdd219) + ], + particleColor: UIColor(rgb: 0xf9b004), + backgroundColor: theme.actionSheet.opaqueItemBackgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: 200.0), + transition: .immediate + ) + } else { + starChild = star.update( + component: StarsImageComponent( + context: component.context, + subject: imageSubject, + theme: theme, + diameter: 90.0, + backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, + icon: imageIcon, + action: !media.isEmpty ? { transitionNode, addToTransitionSurface in + component.openMedia(media.map { $0.media }, transitionNode, addToTransitionSurface) + } : nil + ), + availableSize: CGSize(width: context.availableSize.width, height: 200.0), + transition: .immediate + ) + } + + let amountAttributedText = NSMutableAttributedString(string: amountText, font: countFont, textColor: countColor) let amount = amount.update( component: BalancedTextComponent( text: .plain(amountAttributedText), @@ -572,7 +623,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let amountStar = amountStar.update( component: BundleIconComponent( - name: "Premium/Stars/StarMedium", + name: boostsText != nil ? "Premium/BoostButtonIcon" : "Premium/Stars/StarMedium", tintColor: nil ), availableSize: context.availableSize, @@ -824,17 +875,17 @@ private final class StarsTransactionSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) + + context.add(starChild + .position(CGPoint(x: context.availableSize.width / 2.0, y: starChild.size.height / 2.0 - 19.0)) + ) context.add(title .position(CGPoint(x: context.availableSize.width / 2.0, y: 31.0 + 125.0)) ) - context.add(star - .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 19.0)) - ) - var originY: CGFloat = 0.0 - originY += star.size.height - 23.0 + originY += starChild.size.height - 23.0 var descriptionSize: CGSize = .zero if !descriptionText.isEmpty { @@ -884,7 +935,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { originY += description.size.height + 10.0 } - let amountSpacing: CGFloat = 1.0 + let amountSpacing: CGFloat = countBackgroundColor != nil ? 4.0 : 1.0 var totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width var amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0 if let (statusText, statusColor) = transactionStatus { @@ -928,7 +979,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let amountLabelOriginX: CGFloat let amountStarOriginX: CGFloat - if isSubscription || isSubscriber { + if isSubscription || isSubscriber || boostsText != nil { amountStarOriginX = amountOriginX + amountStar.size.width / 2.0 amountLabelOriginX = amountOriginX + amountStar.size.width + amountSpacing + amount.size.width / 2.0 } else { @@ -936,11 +987,26 @@ private final class StarsTransactionSheetContent: CombinedComponent { amountStarOriginX = amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0 } + var amountLabelOffsetY: CGFloat = 0.0 + var amountStarOffsetY: CGFloat = 0.0 + if let countBackgroundColor { + let amountBackground = amountBackground.update( + component: RoundedRectangle(color: countBackgroundColor, cornerRadius: 23 / 2.0), + availableSize: CGSize(width: totalAmountWidth + 14.0, height: 23.0), + transition: .immediate + ) + context.add(amountBackground + .position(CGPoint(x: context.availableSize.width / 2.0, y: amountOrigin + amount.size.height / 2.0 + 1.0)) + ) + amountLabelOffsetY = 2.0 + amountStarOffsetY = 5.0 + } + context.add(amount - .position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0)) + .position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0 + amountLabelOffsetY)) ) context.add(amountStar - .position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel)) + .position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel + amountStarOffsetY)) ) context.add(table @@ -1166,6 +1232,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { case gift(EngineMessage) case subscription(StarsContext.State.Subscription) case importer(EnginePeer, StarsSubscriptionPricing, PeerInvitationImportersState.Importer, Double) + case boost(EnginePeer.Id, ChannelBoostersContext.State.Boost) } private let context: AccountContext diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 0b1390aebb..658dcf5d12 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -424,7 +424,8 @@ final class StarsTransactionsScreenComponent: Component { UIColor(rgb: 0xf9b004), UIColor(rgb: 0xfdd219) ], - particleColor: UIColor(rgb: 0xf9b004) + particleColor: UIColor(rgb: 0xf9b004), + backgroundColor: environment.theme.list.blocksBackgroundColor )), environment: {}, containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/Contents.json new file mode 100644 index 0000000000..560d4c0836 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "premium_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/premium_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/premium_30.pdf new file mode 100644 index 0000000000..322354940c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/PremiumStar.imageset/premium_30.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index d44e93258b..bcb0c8c7f6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -128,13 +128,15 @@ extension ChatControllerImpl { var canAddToReadingList = true let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url)).count > 1 + var isEmail = false let mailtoString = "mailto:" var openText = self.presentationData.strings.Conversation_LinkDialogOpen - + var copyText = self.presentationData.strings.Conversation_ContextMenuCopyLink if cleanUrl.hasPrefix(mailtoString) { canAddToReadingList = false cleanUrl = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: mailtoString.distance(from: mailtoString.startIndex, to: mailtoString.endIndex))...]) -// isEmail = true + copyText = self.presentationData.strings.Conversation_ContextMenuCopyEmail + isEmail = true } else if canOpenIn { openText = self.presentationData.strings.Conversation_FileOpenIn } @@ -168,16 +170,16 @@ extension ChatControllerImpl { ) items.append( - .action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: copyText, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } - UIPasteboard.general.string = url + UIPasteboard.general.string = cleanUrl - self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: isEmail ? presentationData.strings.Conversation_EmailCopied : presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) })) ) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3894124e8d..d31dfde970 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2791,6 +2791,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsTransactionScreen(context: context, subject: .gift(message)) } + public func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController { + return StarsTransactionScreen(context: context, subject: .boost(peerId, boost)) + } + public func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal { return MiniAppListScreen.initialData(context: context) }