Premium and business screens improvements

This commit is contained in:
Ilya Laktyushin
2024-02-29 01:35:52 +04:00
parent ce3a6c3202
commit 5ce85e7b75
7 changed files with 182 additions and 109 deletions

View File

@@ -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.";

View File

@@ -38,6 +38,7 @@ public enum PremiumIntroSource {
case presence
case readTime
case messageTags
case folderTags
}
public enum PremiumGiftSource: Equatable {

View File

@@ -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))
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)
}
return false })
)
pushControllerImpl?(controller)
})
let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState])

View File

@@ -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 = ""
}

View File

@@ -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),

View File

@@ -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<Empty>] = []
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 {

View File

@@ -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
}