From 4aeab37c7001069f8d4b534b88d2a6730fc539ca Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 24 Feb 2025 19:08:50 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 8 ++ .../Sources/AccountContext.swift | 15 ++- .../AccountContext/Sources/Premium.swift | 2 + .../Sources/Items/ItemListCheckboxItem.swift | 2 + .../Sources/TGPhotoEditorSliderView.m | 2 +- .../ChannelPermissionsController.swift | 12 +-- .../PremiumUI/Sources/PremiumDemoScreen.swift | 27 ++++++ .../PremiumUI/Sources/PremiumGiftScreen.swift | 3 + .../Sources/PremiumIntroScreen.swift | 32 ++++++- .../Sources/PremiumLimitsListScreen.swift | 18 ++++ .../IncomingMessagePrivacyScreen.swift | 54 +++++++---- .../PeerInfo/MessagePriceItem/BUILD | 4 + .../Sources/MessagePriceItem.swift | 88 +++++++++++++++++- .../Perk/PaidMessages.imageset/Contents.json | 12 +++ .../PaidMessages.imageset/paidmessages.pdf | Bin 0 -> 11996 bytes .../Sources/SharedAccountContext.swift | 4 + 16 files changed, 250 insertions(+), 33 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/paidmessages.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 57ed87606b..c0129a94f0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13876,3 +13876,11 @@ Sorry for the inconvenience."; "Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()"; "Chat.InputTextPaidMessagePlaceholder" = "Message for %@"; + +"Privacy.Messages.Stars_1" = "%@ Star"; +"Privacy.Messages.Stars_any" = "%@ Stars"; +"Privacy.Messages.Unlock" = "Unlock with Telegram Premium"; + +"Premium.PaidMessages" = "Paid Messages"; +"Premium.PaidMessagesInfo" = "Charge a fee for messages from non-contacts or new senders."; +"Premium.PaidMessages.Proceed" = "About Telegram Premium"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 57f228240a..578ecd36cd 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1357,25 +1357,29 @@ public struct StarsSubscriptionConfiguration { maxFee: 2500, usdWithdrawRate: 1200, paidMessageMaxAmount: 10000, - paidMessageCommissionPermille: 850 + paidMessageCommissionPermille: 850, + paidMessagesAvailable: false ) } - + public let maxFee: Int64 public let usdWithdrawRate: Int64 public let paidMessageMaxAmount: Int64 public let paidMessageCommissionPermille: Int32 + public let paidMessagesAvailable: Bool fileprivate init( maxFee: Int64, usdWithdrawRate: Int64, paidMessageMaxAmount: Int64, - paidMessageCommissionPermille: Int32 + paidMessageCommissionPermille: Int32, + paidMessagesAvailable: Bool ) { self.maxFee = maxFee self.usdWithdrawRate = usdWithdrawRate self.paidMessageMaxAmount = paidMessageMaxAmount self.paidMessageCommissionPermille = paidMessageCommissionPermille + self.paidMessagesAvailable = paidMessagesAvailable } public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { @@ -1384,11 +1388,14 @@ public struct StarsSubscriptionConfiguration { let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille + let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable + return StarsSubscriptionConfiguration( maxFee: maxFee, usdWithdrawRate: usdWithdrawRate, paidMessageMaxAmount: paidMessageMaxAmount, - paidMessageCommissionPermille: paidMessageCommissionPermille + paidMessageCommissionPermille: paidMessageCommissionPermille, + paidMessagesAvailable: paidMessagesAvailable ) } else { return .defaultValue diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index e237db8ebb..15277eccbc 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -42,6 +42,7 @@ public enum PremiumIntroSource { case folderTags case animatedEmoji case messageEffects + case paidMessages } public enum PremiumGiftSource: Equatable { @@ -79,6 +80,7 @@ public enum PremiumDemoSubject { case folderTags case business case messageEffects + case paidMessages case businessLocation case businessHours diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift index bae1cc72f9..10b25f28dc 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift @@ -347,6 +347,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { iconFrame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0), y: floor((contentSize.height - iconSize.height) / 2.0)), size: iconSize) } strongSelf.imageNode.frame = iconFrame + } else { + strongSelf.imageNode.image = nil } strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)) diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m index e32c11cdb5..4e2e5d3131 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m @@ -141,7 +141,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; if (vertical) startPosition = 2 * visualMargin + visualTotalLength - startPosition; - CGFloat endPosition = visualMargin + visualTotalLength / (_maximumValue - _minimumValue) * (ABS(_minimumValue) + 1.0); + CGFloat endPosition = visualMargin + visualTotalLength / (_maximumValue - _minimumValue) * (ABS(_minimumValue) + _maximumValue); if (vertical) endPosition = 2 * visualMargin + visualTotalLength - endPosition; diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index f41f3921d1..86c02ce352 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -93,7 +93,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case chargeForMessagesInfo(PresentationTheme, String) case messagePriceHeader(PresentationTheme, String) - case messagePrice(PresentationTheme, Int64, String) + case messagePrice(PresentationTheme, Int64, Int64, String) case messagePriceInfo(PresentationTheme, String) case unrestrictBoostersSwitch(PresentationTheme, String, Bool) @@ -241,8 +241,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } else { return false } - case let .messagePrice(lhsTheme, lhsValue, lhsPrice): - if case let .messagePrice(rhsTheme, rhsValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsPrice == rhsPrice { + case let .messagePrice(lhsTheme, lhsValue, lhsMaxValue, lhsPrice): + if case let .messagePrice(rhsTheme, rhsValue, rhsMaxValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsMaxValue == rhsMaxValue, lhsPrice == rhsPrice { return true } else { return false @@ -424,8 +424,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) case let .messagePriceHeader(_, value): return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section) - case let .messagePrice(_, value, price): - return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in + case let .messagePrice(_, value, maxValue, price): + return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value in arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0)) }) case let .messagePriceInfo(_, value): @@ -733,7 +733,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen price = "≈\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice)) - entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, price)) + entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, configuration.paidMessageMaxAmount, price)) entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo("\(configuration.paidMessageCommissionPermille / 10)", price).string)) } } diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index b488cd4d3b..5a01f90490 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1099,6 +1099,25 @@ private final class DemoSheetContent: CombinedComponent { ) ) + availableItems[.paidMessages] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.paidMessages, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoFile: configuration.videos["paid_messages"], + decoration: .badgeStars + )), + title: strings.Premium_PaidMessages, + text: strings.Premium_PaidMessagesInfo, + textColor: textColor + ) + ) + ) + ) + let index: Int = 0 var items: [DemoPagerComponent.Item] = [] if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { @@ -1195,6 +1214,8 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_FolderTagsStandaloneInfo case .messageEffects: text = strings.Premium_MessageEffectsInfo + case .paidMessages: + text = strings.Premium_PaidMessagesInfo default: text = "" } @@ -1279,6 +1300,9 @@ private final class DemoSheetContent: CombinedComponent { case .emojiStatus: buttonText = strings.Premium_EmojiStatus_Proceed buttonAnimationName = "premium_unlock" + case .paidMessages: + buttonText = strings.Premium_PaidMessages_Proceed + buttonAnimationName = "premium_unlock" default: buttonText = strings.Common_OK } @@ -1468,6 +1492,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case business case folderTags case messageEffects + case paidMessages case businessLocation case businessHours @@ -1526,6 +1551,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return .folderTags case .messageEffects: return .messageEffects + case .paidMessages: + return .paidMessages case .businessLocation: return .businessLocation case .businessHours: diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 99de9f72af..5d3c60997c 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -433,6 +433,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), + UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), UIColor(rgb: 0xab4ac4), UIColor(rgb: 0xa34cd7), @@ -538,6 +539,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .messagePrivacy case .messageEffects: demoSubject = .messageEffects + case .paidMessages: + demoSubject = .paidMessages case .business: demoSubject = .business default: diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 4b4d8d2593..a1585b91cf 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -302,6 +302,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .paidMessages: + if case .messageEffects = rhs { + return true + } else { + return false + } } } @@ -349,6 +355,7 @@ public enum PremiumSource: Equatable { case messageTags case folderTags case messageEffects + case paidMessages var identifier: String? { switch self { @@ -442,6 +449,8 @@ public enum PremiumSource: Equatable { return "folder_tags" case .messageEffects: return "effects" + case .paidMessages: + return "paid_messages" } } } @@ -470,6 +479,7 @@ public enum PremiumPerk: CaseIterable { case business case folderTags case messageEffects + case paidMessages case businessLocation case businessHours @@ -504,7 +514,8 @@ public enum PremiumPerk: CaseIterable { .messagePrivacy, .folderTags, .business, - .messageEffects + .messageEffects, + .paidMessages ] } @@ -578,6 +589,8 @@ public enum PremiumPerk: CaseIterable { return "folder_tags" case .messageEffects: return "effects" + case .paidMessages: + return "paid_messages" case .business: return "business" case .businessLocation: @@ -647,6 +660,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_Business case .messageEffects: return strings.Premium_MessageEffects + case .paidMessages: + return strings.Premium_PaidMessages case .businessLocation: return strings.Business_Location case .businessHours: @@ -714,6 +729,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_BusinessInfo case .messageEffects: return strings.Premium_MessageEffectsInfo + case .paidMessages: + return strings.Premium_PaidMessagesInfo case .businessLocation: return strings.Business_LocationInfo case .businessHours: @@ -781,7 +798,8 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/Business" case .messageEffects: return "Premium/Perk/MessageEffects" - + case .paidMessages: + return "Premium/Perk/PaidMessages" case .businessLocation: return "Premium/BusinessPerk/Location" case .businessHours: @@ -819,6 +837,7 @@ struct PremiumIntroConfiguration { .colors, .wallpapers, .profileBadge, + .paidMessages, .messagePrivacy, .advancedChatManagement, .noAds, @@ -867,6 +886,12 @@ struct PremiumIntroConfiguration { perks = PremiumIntroConfiguration.defaultValue.perks } + #if DEBUG + if !perks.contains(.paidMessages) { + perks.append(.paidMessages) + } + #endif + var businessPerks: [PremiumPerk] = [] if let values = data["business_promo_order"] as? [String] { for value in values { @@ -1859,6 +1884,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), + UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), UIColor(rgb: 0xab4ac4), UIColor(rgb: 0xa34cd7), @@ -2092,6 +2118,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .messagePrivacy case .messageEffects: demoSubject = .messageEffects + case .paidMessages: + demoSubject = .paidMessages case .business: demoSubject = .business let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 5fd01d8666..bbf34ff84e 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -842,6 +842,24 @@ public class PremiumLimitsListScreen: ViewController { ) ) ) + availableItems[.paidMessages] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.paidMessages, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: videos["paid_messages"], + decoration: .badgeStars + )), + title: strings.Premium_PaidMessages, + text: strings.Premium_PaidMessagesInfo, + textColor: textColor + ) + ) + ) + ) availableItems[.business] = DemoPagerComponent.Item( AnyComponentWithIdentity( id: PremiumDemoScreen.Subject.business, diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index e7b6151db8..b3be316415 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -19,19 +19,22 @@ private final class IncomingMessagePrivacyScreenArguments { let disabledValuePressed: () -> Void let infoLinkAction: () -> Void let openExceptions: () -> Void + let openPremiumInfo: () -> Void init( context: AccountContext, updateValue: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void, disabledValuePressed: @escaping () -> Void, infoLinkAction: @escaping () -> Void, - openExceptions: @escaping () -> Void + openExceptions: @escaping () -> Void, + openPremiumInfo: @escaping () -> Void ) { self.context = context self.updateValue = updateValue self.disabledValuePressed = disabledValuePressed self.infoLinkAction = infoLinkAction self.openExceptions = openExceptions + self.openPremiumInfo = openPremiumInfo } } @@ -49,7 +52,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case optionChargeForMessages(value: GlobalPrivacySettings.NonContactChatsPrivacy, isEnabled: Bool) case footer(value: GlobalPrivacySettings.NonContactChatsPrivacy) case priceHeader - case price(value: Int64, price: String) + case price(value: Int64, maxValue: Int64, price: String, isEnabled: Bool) case priceInfo(commission: Int32, value: String) case exceptionsHeader case exceptions(count: Int) @@ -128,12 +131,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { if case .paidMessages = value { isChecked = true } - return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: { - if isEnabled { - arguments.updateValue(.paidMessages(StarsAmount(value: 400, nanos: 0))) - } else { - arguments.disabledValuePressed() - } + return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled || isChecked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateValue(.paidMessages(StarsAmount(value: 400, nanos: 0))) }) case let .footer(value): let text: String @@ -149,9 +148,11 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { }) case .priceHeader: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_MessagePrice, sectionId: self.section) - case let .price(value, price): - return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in + case let .price(value, maxValue, price, isEnabled): + return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value in arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0))) + }, openPremiumInfo: { + arguments.openPremiumInfo() }) case let .priceInfo(commission, value): return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo("\(commission)", value).string), sectionId: self.section) @@ -178,7 +179,9 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD entries.append(.header) entries.append(.optionEverybody(value: state.updatedValue)) entries.append(.optionPremium(value: state.updatedValue, isEnabled: enableSetting)) - entries.append(.optionChargeForMessages(value: state.updatedValue, isEnabled: isPremium)) + if configuration.paidMessagesAvailable { + entries.append(.optionChargeForMessages(value: state.updatedValue, isEnabled: isPremium)) + } if case let .paidMessages(amount) = state.updatedValue { entries.append(.footer(value: state.updatedValue)) @@ -188,11 +191,14 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD let price = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" - entries.append(.price(value: amount.value, price: price)) + entries.append(.price(value: amount.value, maxValue: configuration.paidMessageMaxAmount, price: price, isEnabled: isPremium)) entries.append(.priceInfo(commission: configuration.paidMessageCommissionPermille / 10, value: price)) - entries.append(.exceptionsHeader) - entries.append(.exceptions(count: state.disableFor.count)) - entries.append(.exceptionsInfo) + + if isPremium { + entries.append(.exceptionsHeader) + entries.append(.exceptions(count: state.disableFor.count)) + entries.append(.exceptionsInfo) + } } else { entries.append(.footer(value: state.updatedValue)) entries.append(.info) @@ -347,6 +353,17 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP }) pushControllerImpl?(controller) } + }, + openPremiumInfo: { + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .paidMessages, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .paidMessages, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + pushControllerImpl?(controller) } ) @@ -414,7 +431,12 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP controller?.push(c) } controller.attemptNavigation = { _ in - update(stateValue.with({ $0 }).updatedValue) + let updatedValue = stateValue.with({ $0 }).updatedValue + if !context.isPremium, case .paidMessages = updatedValue { + + } else { + update(updatedValue) + } return true } dismissImpl = { [weak controller] in diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD index 540a813d62..adbe07190b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD @@ -19,6 +19,10 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/ItemListUI", "//submodules/LegacyComponents", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index 2e4e2978e7..598a80f7e0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -8,6 +8,10 @@ import TelegramPresentationData import LegacyComponents import ItemListUI import PresentationDataUtils +import ComponentFlow +import ButtonComponent +import BundleIconComponent +import MultilineTextComponent private let textFont = Font.with(size: 17.0, traits: .monospacedNumbers) private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers) @@ -15,22 +19,26 @@ private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers) public final class MessagePriceItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let isEnabled: Bool let minValue: Int64 let maxValue: Int64 let value: Int64 let price: String public let sectionId: ItemListSectionId let updated: (Int64) -> Void + let openPremiumInfo: (() -> Void)? - public init(theme: PresentationTheme, strings: PresentationStrings, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void) { + public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void, openPremiumInfo: (() -> Void)? = nil) { self.theme = theme self.strings = strings + self.isEnabled = isEnabled self.minValue = minValue self.maxValue = maxValue self.value = value self.price = price self.sectionId = sectionId self.updated = updated + self.openPremiumInfo = openPremiumInfo } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { @@ -155,6 +163,9 @@ private class MessagePriceItemNode: ListViewItemNode { private let rightTextNode: ImmediateTextNode private let centerLeftTextNode: ImmediateTextNode private let centerRightTextNode: ImmediateTextNode + private let lockIconNode: ASImageNode + + private let button: ComponentView private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true) @@ -178,12 +189,18 @@ private class MessagePriceItemNode: ListViewItemNode { self.centerLeftTextNode = ImmediateTextNode() self.centerRightTextNode = ImmediateTextNode() + self.lockIconNode = ASImageNode() + self.lockIconNode.displaysAsynchronously = false + + self.button = ComponentView() + super.init(layerBacked: false, dynamicBounce: false) self.addSubnode(self.leftTextNode) self.addSubnode(self.rightTextNode) self.addSubnode(self.centerLeftTextNode) self.addSubnode(self.centerRightTextNode) + self.addSubnode(self.lockIconNode) } override func didLoad() { @@ -224,11 +241,15 @@ private class MessagePriceItemNode: ListViewItemNode { themeUpdated = true } - let contentSize: CGSize + var contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel contentSize = CGSize(width: params.width, height: 88.0) + if !item.isEnabled { + contentSize.height = 166.0 + } + insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -290,8 +311,7 @@ private class MessagePriceItemNode: ListViewItemNode { strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) - //TODO:localize - let centralLeftText = "\(item.value) Stars" + let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value)) strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.theme.list.itemPrimaryTextColor) strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.theme.list.itemSecondaryTextColor) @@ -323,6 +343,66 @@ private class MessagePriceItemNode: ListViewItemNode { sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0)) } + + strongSelf.lockIconNode.isHidden = item.isEnabled + if !item.isEnabled { + if themeUpdated { + strongSelf.lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: item.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.5)) + } + if let image = strongSelf.lockIconNode.image { + strongSelf.lockIconNode.frame = CGRect(origin: CGPoint(x: centerLeftFrame.minX - image.size.width - 1.0, y: 12.0 + UIScreenPixel), size: image.size) + } + + let sideInset: CGFloat = 16.0 + let buttonSize = CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: 50.0) + let _ = strongSelf.button.update( + transition: .immediate, + component: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + color: item.theme.list.itemCheckColors.fillColor, + foreground: item.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("unlock"), + component: AnyComponent( + HStack([ + AnyComponentWithIdentity( + id: AnyHashable("icon"), + component: AnyComponent(BundleIconComponent(name: "Chat/Stickers/Lock", tintColor: item.theme.list.itemCheckColors.foregroundColor)) + ), + AnyComponentWithIdentity( + id: AnyHashable("label"), + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: item.strings.Privacy_Messages_Unlock, font: Font.semibold(17.0), textColor: item.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) + ) + ], spacing: 3.0) + ) + ), + isEnabled: true, + tintWhenDisabled: false, + allowActionWhenDisabled: false, + displaysProgress: false, + action: { [weak self] in + guard let self, let item = self.item else { + return + } + item.openPremiumInfo?() + } + ) + ), + environment: {}, + containerSize: buttonSize + ) + if let buttonView = strongSelf.button.view { + if buttonView.superview == nil { + strongSelf.view.addSubview(buttonView) + } + buttonView.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: contentSize.height - buttonSize.height - sideInset), size: buttonSize) + } + } else if let buttonView = strongSelf.button.view, buttonView.superview != nil { + buttonView.removeFromSuperview() + } } }) } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/Contents.json new file mode 100644 index 0000000000..2b5437edde --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "paidmessages.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/paidmessages.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/paidmessages.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e743406c7421c9d8a37a3fa867d401ede6e65ff9 GIT binary patch literal 11996 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c21c2a2938..8e698ec1c7 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2486,6 +2486,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .messageEffects case .animatedEmoji: mappedSource = .animatedEmoji + case .paidMessages: + mappedSource = .paidMessages } let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark) controller.wasDismissed = dismissed @@ -2542,6 +2544,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .folderTags case .messageEffects: mappedSubject = .messageEffects + case .paidMessages: + mappedSubject = .paidMessages case .business: mappedSubject = .business buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton