diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 42e90545a5..2af89cfaec 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11316,6 +11316,11 @@ Sorry for the inconvenience."; "ChannelBoost.Header.Giveaway" = "giveaway"; "ChannelBoost.Header.Features" = "features"; +"Premium.FolderTags" = "Folder Tags"; +"Premium.FolderTagsInfo" = "Add colorful labels to chats for faster access with Telegram Premium."; +"Premium.FolderTagsStandaloneInfo" = "Add colorful labels to chats for faster access with Telegram Premium."; +"Premium.FolderTags.Proceed" = "About Telegram Premium"; + "Premium.Business" = "Telegram Business"; "Premium.BusinessInfo" = "Upgrade your account with business features such as location, opening hours and quick replies."; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index a23c4a9523..c327bc5597 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -38,6 +38,7 @@ public enum PremiumIntroSource { case presence case readTime case messageTags + case folderTags } public enum PremiumGiftSource: Equatable { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 3b496c6b11..8ed12be0da 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -558,14 +558,15 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch }, updateDisplayTags: { value in context.engine.peers.updateChatListFiltersDisplayTags(isEnabled: value) }, updateDisplayTagsLocked: { - //TODO:localize - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: "Subscribe to **Telegram Premium** to show folder tags.", customUndoText: presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - if case .undo = action { - pushControllerImpl?(PremiumIntroScreen(context: context, source: .folders)) - } - return false }) - ) + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folderTags, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + pushControllerImpl?(controller) }) let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 5504cdc4d8..5957a9920f 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1004,7 +1004,7 @@ private final class DemoSheetContent: CombinedComponent { position: .top, model: .island, videoFile: configuration.videos["last_seen"], - decoration: .tag + decoration: .badgeStars )), title: strings.Premium_LastSeen, text: strings.Premium_LastSeenInfo, @@ -1023,7 +1023,7 @@ private final class DemoSheetContent: CombinedComponent { position: .top, model: .island, videoFile: configuration.videos["message_privacy"], - decoration: .tag + decoration: .swirlStars )), title: strings.Premium_MessagePrivacy, text: strings.Premium_MessagePrivacyInfo, @@ -1033,6 +1033,26 @@ private final class DemoSheetContent: CombinedComponent { ) ) + availableItems[.folderTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.folderTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["folder_tags"], + decoration: .tag + )), + title: strings.Premium_FolderTags, + text: strings.Premium_FolderTagsStandaloneInfo, + textColor: textColor + ) + ) + ) + ) + let index: Int = 0 var items: [DemoPagerComponent.Item] = [] if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { @@ -1136,6 +1156,8 @@ private final class DemoSheetContent: CombinedComponent { buttonText = strings.Premium_LastSeen_Proceed case .messagePrivacy: buttonText = strings.Premium_MessagePrivacy_Proceed + case .folderTags: + buttonText = strings.Premium_FolderTags_Proceed default: buttonText = strings.Common_OK } @@ -1177,6 +1199,8 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_LastSeenInfo case .messagePrivacy: text = strings.Premium_MessagePrivacyInfo + case .folderTags: + text = strings.Premium_FolderTagsStandaloneInfo default: text = "" } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index d2afc9fc9c..2e81b49ed3 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -435,6 +435,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x676bff), //replace UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 21d6f91c16..11e56087a1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -282,6 +282,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .folderTags: + if case .folderTags = rhs { + return true + } else { + return false + } } } @@ -326,6 +332,7 @@ public enum PremiumSource: Equatable { case presence case readTime case messageTags + case folderTags var identifier: String? { switch self { @@ -413,6 +420,8 @@ public enum PremiumSource: Equatable { return "read_time" case .messageTags: return "saved_tags" + case .folderTags: + return "folder_tags" } } } @@ -448,7 +457,6 @@ public enum PremiumPerk: CaseIterable { case businessAwayMessage case businessChatBots - public static var allCases: [PremiumPerk] { return [ .doubleLimits, @@ -471,12 +479,28 @@ public enum PremiumPerk: CaseIterable { .messageTags, .lastSeen, .messagePrivacy, + .folderTags, .business ] } - init?(identifier: String) { - for perk in PremiumPerk.allCases { + public static var allBusinessCases: [PremiumPerk] { + return [ + .businessLocation, + .businessHours, + .businessGreetingMessage, + .businessQuickReplies, + .businessAwayMessage, + .businessChatBots, +// .emojiStatus, +// .folderTags, +// .stories, + ] + } + + + init?(identifier: String, business: Bool) { + for perk in business ? PremiumPerk.allBusinessCases : PremiumPerk.allCases { if perk.identifier == identifier { self = perk return @@ -527,10 +551,22 @@ public enum PremiumPerk: CaseIterable { return "last_seen" case .messagePrivacy: return "message_privacy" + case .folderTags: + return "folder_tags" case .business: return "business" - default: - return "" + case .businessLocation: + return "location" + case .businessHours: + return "opening_hours" + case .businessQuickReplies: + return "quick_replies" + case .businessGreetingMessage: + return "greeting_messages" + case .businessAwayMessage: + return "away_messages" + case .businessChatBots: + return "chatbots" } } @@ -576,10 +612,23 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_LastSeen case .messagePrivacy: return strings.Premium_MessagePrivacy + case .folderTags: + return strings.Premium_FolderTags case .business: return strings.Premium_Business - default: - return "" + + case .businessLocation: + return strings.Business_Location + case .businessHours: + return strings.Business_OpeningHours + case .businessQuickReplies: + return strings.Business_QuickReplies + case .businessGreetingMessage: + return strings.Business_GreetingMessages + case .businessAwayMessage: + return strings.Business_AwayMessages + case .businessChatBots: + return strings.Business_Chatbots } } @@ -625,10 +674,23 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_LastSeenInfo case .messagePrivacy: return strings.Premium_MessagePrivacyInfo + case .folderTags: + return strings.Premium_FolderTagsInfo case .business: return strings.Premium_BusinessInfo - default: - return "" + + case .businessLocation: + return strings.Business_LocationInfo + case .businessHours: + return strings.Business_OpeningHoursInfo + case .businessQuickReplies: + return strings.Business_QuickRepliesInfo + case .businessGreetingMessage: + return strings.Business_GreetingMessagesInfo + case .businessAwayMessage: + return strings.Business_AwayMessagesInfo + case .businessChatBots: + return strings.Business_ChatbotsInfo } } @@ -674,86 +736,22 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/LastSeen" case .messagePrivacy: return "Premium/Perk/MessagePrivacy" + case .folderTags: + return "Premium/Perk/MessageTags" case .business: return "Premium/Perk/Business" - default: - return "" - } - } -} - -private enum BusinessPerk: CaseIterable { - case location - case hours - case quickReplies - case greetings - case awayMessages - case chatbots - - var identifier: String { - switch self { - case .location: - return "location" - case .hours: - return "opening_hours" - case .quickReplies: - return "quick_replies" - case .greetings: - return "greeting_messages" - case .awayMessages: - return "away_messages" - case .chatbots: - return "chatbots" - } - } - - func title(strings: PresentationStrings) -> String { - switch self { - case .location: - return strings.Business_Location - case .hours: - return strings.Business_OpeningHours - case .quickReplies: - return strings.Business_QuickReplies - case .greetings: - return strings.Business_GreetingMessages - case .awayMessages: - return strings.Business_AwayMessages - case .chatbots: - return strings.Business_Chatbots - } - } - - func subtitle(strings: PresentationStrings) -> String { - switch self { - case .location: - return strings.Business_LocationInfo - case .hours: - return strings.Business_OpeningHoursInfo - case .quickReplies: - return strings.Business_QuickRepliesInfo - case .greetings: - return strings.Business_GreetingMessagesInfo - case .awayMessages: - return strings.Business_AwayMessagesInfo - case .chatbots: - return strings.Business_ChatbotsInfo - } - } - - var iconName: String { - switch self { - case .location: + + case .businessLocation: return "Premium/BusinessPerk/Location" - case .hours: + case .businessHours: return "Premium/BusinessPerk/Hours" - case .quickReplies: + case .businessQuickReplies: return "Premium/BusinessPerk/Replies" - case .greetings: + case .businessGreetingMessage: return "Premium/BusinessPerk/Greetings" - case .awayMessages: + case .businessAwayMessage: return "Premium/BusinessPerk/Away" - case .chatbots: + case .businessChatBots: return "Premium/BusinessPerk/Chatbots" } } @@ -783,20 +781,32 @@ struct PremiumIntroConfiguration { .animatedUserpics, .premiumStickers, .business + ], businessPerks: [ + .businessGreetingMessage, + .businessAwayMessage, + .businessQuickReplies, + .businessChatBots, + .businessHours, + .businessLocation +// .emojiStatus, +// .folderTags, +// .stories ]) } let perks: [PremiumPerk] + let businessPerks: [PremiumPerk] - fileprivate init(perks: [PremiumPerk]) { + fileprivate init(perks: [PremiumPerk], businessPerks: [PremiumPerk]) { self.perks = perks + self.businessPerks = businessPerks } public static func with(appConfiguration: AppConfiguration) -> PremiumIntroConfiguration { if let data = appConfiguration.data, let values = data["premium_promo_order"] as? [String] { var perks: [PremiumPerk] = [] for value in values { - if let perk = PremiumPerk(identifier: value) { + if let perk = PremiumPerk(identifier: value, business: false) { if !perks.contains(perk) { perks.append(perk) } else { @@ -825,7 +835,29 @@ struct PremiumIntroConfiguration { perks.append(.business) } #endif - return PremiumIntroConfiguration(perks: perks) + + + var businessPerks: [PremiumPerk] = [] + if let values = data["business_promo_order"] as? [String] { + for value in values { + if let perk = PremiumPerk(identifier: value, business: true) { + if !businessPerks.contains(perk) { + businessPerks.append(perk) + } else { + businessPerks = [] + break + } + } else { + businessPerks = [] + break + } + } + } + if businessPerks.count < 4 { + businessPerks = PremiumIntroConfiguration.defaultValue.businessPerks + } + + return PremiumIntroConfiguration(perks: perks, businessPerks: businessPerks) } else { return .defaultValue } @@ -1798,6 +1830,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x676bff), //replace UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), @@ -2109,7 +2142,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var i = 0 var perksItems: [AnyComponentWithIdentity] = [] - for perk in BusinessPerk.allCases { + for perk in state.configuration.businessPerks { perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ @@ -2140,7 +2173,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let isPremium = state?.isPremium == true if isPremium { switch perk { - case .location: + case .businessLocation: let _ = (accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId) ) @@ -2150,7 +2183,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in })) }) - case .hours: + case .businessHours: let _ = (accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId) ) @@ -2160,7 +2193,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in })) }) - case .quickReplies: + case .businessQuickReplies: let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2169,7 +2202,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData)) }) - case .greetings: + case .businessGreetingMessage: let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2178,7 +2211,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: false)) }) - case .awayMessages: + case .businessAwayMessage: let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2187,7 +2220,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: true)) }) - case .chatbots: + case .businessChatBots: let _ = (accountContext.sharedContext.makeChatbotSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2196,25 +2229,29 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData)) }) + default: + fatalError() } } else { var demoSubject: PremiumDemoScreen.Subject switch perk { - case .location: + case .businessLocation: demoSubject = .businessLocation - case .hours: + case .businessHours: demoSubject = .businessHours - case .quickReplies: + case .businessQuickReplies: demoSubject = .businessQuickReplies - case .greetings: + case .businessGreetingMessage: demoSubject = .businessGreetingMessage - case .awayMessages: + case .businessAwayMessage: demoSubject = .businessAwayMessage - case .chatbots: + case .businessChatBots: demoSubject = .businessChatBots + default: + fatalError() } var dismissImpl: (() -> Void)? - let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark) + let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.businessPerks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark) controller.action = { [weak state] in dismissImpl?() if state?.isPremium == false { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 72ec09c895..f454f44991 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2000,6 +2000,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .readTime case .messageTags: mappedSource = .messageTags + case .folderTags: + mappedSource = .folderTags } let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark) controller.wasDismissed = dismissed @@ -2049,6 +2051,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .lastSeen case .messagePrivacy: mappedSubject = .messagePrivacy + case .folderTags: + mappedSubject = .folderTags default: mappedSubject = .doubleLimits }