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 0000000000..e743406c74 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Perk/PaidMessages.imageset/paidmessages.pdf differ 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