From 80cd8f7b320652152349e7ce3494f4bbd96550e6 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Mar 2025 06:49:43 +0400 Subject: [PATCH] Various improvements --- .../Sources/AccountContext.swift | 2 + submodules/AuthorizationUI/BUILD | 1 + .../AuthorizationSequenceController.swift | 5 +- .../AuthorizationSequencePaymentScreen.swift | 339 +++++++- .../Sources/InAppPurchaseManager.swift | 2 + submodules/PremiumUI/BUILD | 1 + .../Sources/BusinessPageComponent.swift | 1 + .../Sources/PremiumIntroScreen.swift | 746 +++++++++++------- .../Sources/PremiumLimitsListScreen.swift | 1 + submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api29.swift | 18 +- .../Sources/Account/Account.swift | 95 ++- .../TelegramCore/Sources/Authorization.swift | 25 +- .../UnauthorizedAccountStateManager.swift | 18 +- .../SyncCore_UnauthorizedAccountState.swift | 12 +- .../TelegramEngine/Payments/AppStore.swift | 23 +- .../Payments/TelegramEnginePayments.swift | 2 +- .../ChatEmptyNode/Sources/ChatEmptyNode.swift | 23 +- .../ChatMessageAnimatedStickerItemNode.swift | 4 + .../ChatMessageAttachedContentNode.swift | 4 + .../Sources/ChatMessageBubbleItemNode.swift | 6 +- .../ChatMessageContactBubbleContentNode.swift | 4 + .../ChatMessageDateAndStatusNode.swift | 106 +++ ...hatMessageFactCheckBubbleContentNode.swift | 4 + ...ChatMessageGiveawayBubbleContentNode.swift | 1 + .../ChatMessageInteractiveFileNode.swift | 4 + ...atMessageInteractiveInstantVideoNode.swift | 4 + .../ChatMessageInteractiveMediaNode.swift | 4 + .../ChatMessageMapBubbleContentNode.swift | 4 + .../ChatMessageMediaBubbleContentNode.swift | 4 + .../ChatMessagePollBubbleContentNode.swift | 4 + ...atMessageRestrictedBubbleContentNode.swift | 4 + .../Sources/ChatMessageStickerItemNode.swift | 4 + .../ChatMessageTextBubbleContentNode.swift | 4 + .../AffiliateProgramSetupScreen/BUILD | 2 +- .../Sources/AffiliateProgramSetupScreen.swift | 2 +- .../Premium/PremiumCoinComponent/BUILD | 27 + .../Sources/PremiumCoinComponent.swift | 5 +- .../Premium/Authorization/Contents.json | 9 + .../Authorization/Cost.imageset/Contents.json | 12 + .../Cost.imageset/highprice_30.pdf | Bin 0 -> 4940 bytes .../Support.imageset/Contents.json | 12 + .../Support.imageset/support_30.pdf | Bin 0 -> 14530 bytes .../Verification.imageset/Contents.json | 12 + .../verificationcode_30.pdf | Bin 0 -> 4908 bytes .../Sources/SharedAccountContext.swift | 24 +- 46 files changed, 1200 insertions(+), 386 deletions(-) create mode 100644 submodules/TelegramUI/Components/Premium/PremiumCoinComponent/BUILD rename submodules/{PremiumUI => TelegramUI/Components/Premium/PremiumCoinComponent}/Sources/PremiumCoinComponent.swift (99%) create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/highprice_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Support.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Support.imageset/support_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/verificationcode_30.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index eacb803483..aaa6ae47f3 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1053,6 +1053,8 @@ public protocol SharedAccountContext: AnyObject { func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController + func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController + func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index db3854ecc2..a0657ad3e9 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -43,6 +43,7 @@ swift_library( "//submodules/MoreButtonNode:MoreButtonNode", "//submodules/ContextUI:ContextUI", "//submodules/InAppPurchaseManager", + "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 47c1c5b875..0ddd8a45c6 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -764,12 +764,11 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } private func paymentController(number: String, phoneCodeHash: String, storeProduct: String) -> AuthorizationSequencePaymentScreen { - let controller = AuthorizationSequencePaymentScreen(engine: self.engine, presentationData: self.presentationData, inAppPurchaseManager: self.inAppPurchaseManager, phoneNumber: number, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, back: { [weak self] in + let controller = AuthorizationSequencePaymentScreen(sharedContext: self.sharedContext, engine: self.engine, presentationData: self.presentationData, inAppPurchaseManager: self.inAppPurchaseManager, phoneNumber: number, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, back: { [weak self] in guard let self else { return } let countryCode = AuthorizationSequenceController.defaultCountryCode() - let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) return controller @@ -1302,7 +1301,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel)) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) - case let .payment(number, codeHash, storeProduct): + case let .payment(number, codeHash, storeProduct, _): var controllers: [ViewController] = [] if !self.otherAccountPhoneNumbers.1.isEmpty { controllers.append(self.splashController()) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift index 55efdae6f7..914bfb8934 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift @@ -14,15 +14,19 @@ import ViewControllerComponent import MultilineTextComponent import BalancedTextComponent import BundleIconComponent -import LottieComponent import ButtonComponent import TextFormat import InAppPurchaseManager import ConfettiEffect +import PremiumCoinComponent +import Markdown +import CountrySelectionUI +import AccountContext final class AuthorizationSequencePaymentScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment + let sharedContext: SharedAccountContext let engine: TelegramEngineUnauthorized let inAppPurchaseManager: InAppPurchaseManager let presentationData: PresentationData @@ -31,6 +35,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { let storeProduct: String init( + sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, presentationData: PresentationData, @@ -38,6 +43,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { phoneCodeHash: String, storeProduct: String ) { + self.sharedContext = sharedContext self.engine = engine self.inAppPurchaseManager = inAppPurchaseManager self.presentationData = presentationData @@ -93,9 +99,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { self.state?.updated() let (currency, amount) = storeProduct.priceCurrencyAndAmount - let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount) - let _ = (component.engine.payments.canPurchasePremium(purpose: purpose) |> deliverOnMainQueue).start(next: { [weak self] available in guard let self else { @@ -111,6 +115,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { guard let self, let controller = self.environment?.controller() else { return } + self.inProgress = false self.state?.updated(transition: .immediate) var errorText: String? @@ -156,7 +161,6 @@ final class AuthorizationSequencePaymentScreenComponent: Component { let environment = environment[EnvironmentType.self].value let themeUpdated = self.environment?.theme !== environment.theme self.environment = environment - self.component = component self.state = state if self.component == nil { @@ -166,38 +170,150 @@ final class AuthorizationSequencePaymentScreenComponent: Component { return } self.products = products - self.state?.updated() + if !self.isUpdating { + self.state?.updated() + } }) } + self.component = component + if themeUpdated { self.backgroundColor = environment.theme.list.plainBackgroundColor } + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let animationHeight: CGFloat = 120.0 let animationSize = self.animation.update( transition: transition, - component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "Coin"), - startingPosition: .begin + component: AnyComponent(PremiumCoinComponent( + mode: .business, + isIntro: true, + isVisible: true, + hasIdleAnimations: true )), environment: {}, - containerSize: CGSize(width: animationHeight, height: animationHeight) + containerSize: CGSize(width: min(414.0, availableSize.width), height: 184.0) ) + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: "SMS Fee", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor))) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + + let textColor = environment.theme.list.itemPrimaryTextColor + let secondaryTextColor = environment.theme.list.itemSecondaryTextColor + let linkColor = environment.theme.list.itemAccentColor + + var countryName: String = "" + if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(component.phoneNumber, preferredCountries: [:]) { + countryName = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: environment.strings) ?? country.name + } + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: "cost", + component: AnyComponent(ParagraphComponent( + title: "High SMS Costs", + titleColor: textColor, + text: "Telecom providers in your country (\(countryName)) charge Telegram very high prices for SMS.", + textColor: secondaryTextColor, + iconName: "Premium/Authorization/Cost", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "verification", + component: AnyComponent(ParagraphComponent( + title: "Verification Required", + titleColor: textColor, + text: "Telegram needs to send you an SMS with a verification code to confirm your phone number.", + textColor: secondaryTextColor, + iconName: "Premium/Authorization/Verification", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "withdrawal", + component: AnyComponent(ParagraphComponent( + title: "Support via [Telegram Premium >]()", + titleColor: textColor, + text: "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.", + textColor: secondaryTextColor, + iconName: "Premium/Authorization/Support", + iconColor: linkColor, + action: { [weak self] in + guard let self, let controller = self.environment?.controller() else { + return + } + let introController = component.sharedContext.makePremiumIntroController( + sharedContext: component.sharedContext, + engine: component.engine, + inAppPurchaseManager: component.inAppPurchaseManager, + source: .about, + dismissed: nil + ) + controller.push(introController) + } + )) + ) + ) + + let listSize = self.list.update( + transition: transition, + component: AnyComponent(List(items)), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + + let titleSpacing: CGFloat = -24.0 + let listSpacing: CGFloat = 12.0 + let totalHeight = animationSize.height + titleSpacing + titleSize.height + listSpacing + listSize.height + + var originY = floor((availableSize.height - totalHeight) / 2.0) + if let animationView = self.animation.view { if animationView.superview == nil { self.addSubview(animationView) } - animationView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) / 2.0), y: 156.0), size: animationSize) + animationView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) / 2.0), y: originY), size: animationSize) + originY += animationSize.height + titleSpacing + } + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: originY), size: titleSize) + originY += titleSize.height + listSpacing + } + + if let listView = self.list.view { + if listView.superview == nil { + self.addSubview(listView) + } + listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize) } let buttonHeight: CGFloat = 50.0 let bottomPanelPadding: CGFloat = 12.0 let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset - - let sideInset: CGFloat = 16.0 - let buttonString = "Sign up for $1" + + let priceString: String + if let product = self.products.first(where: { $0.id == component.storeProduct }) { + priceString = product.price + } else { + priceString = "–" + } + let buttonString = "Sign up for \(priceString)" let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let buttonSize = self.button.update( transition: transition, @@ -210,7 +326,12 @@ final class AuthorizationSequencePaymentScreenComponent: Component { ), content: AnyComponentWithIdentity( id: AnyHashable(buttonString), - component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + component: AnyComponent( + VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.regular(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center))))) + ], spacing: 1.0) + ) ), isEnabled: true, displaysProgress: self.inProgress, @@ -243,6 +364,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer { public init( + sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, presentationData: PresentationData, inAppPurchaseManager: InAppPurchaseManager, @@ -252,6 +374,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo back: @escaping () -> Void ) { super.init(component: AuthorizationSequencePaymentScreenComponent( + sharedContext: sharedContext, engine: engine, inAppPurchaseManager: inAppPurchaseManager, presentationData: presentationData, @@ -260,6 +383,12 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo storeProduct: storeProduct ), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData))) + loadServerCountryCodes(accountManager: sharedContext.accountManager, engine: engine, completion: { [weak self] in + if let strongSelf = self { + strongSelf.requestLayout(forceUpdate: true, transition: .immediate) + } + }) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -285,3 +414,183 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo self.dismiss() } } + +private final class ParagraphComponent: CombinedComponent { + let title: String + let titleColor: UIColor + let text: String + let textColor: UIColor + let iconName: String + let iconColor: UIColor + let action: (() -> Void)? + + public init( + title: String, + titleColor: UIColor, + text: String, + textColor: UIColor, + iconName: String, + iconColor: UIColor, + action: (() -> Void)? = nil + ) { + self.title = title + self.titleColor = titleColor + self.text = text + self.textColor = textColor + self.iconName = iconName + self.iconColor = iconColor + self.action = action + } + + static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.titleColor != rhs.titleColor { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconColor != rhs.iconColor { + return false + } + return true + } + + final class State: ComponentState { + var cachedChevronImage: (UIImage, UIColor)? + } + + func makeState() -> State { + return State() + } + + static var body: Body { + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + let component = context.component + let state = context.state + + let leftInset: CGFloat = 64.0 + let rightInset: CGFloat = 32.0 + let textSideInset: CGFloat = leftInset + 8.0 + let spacing: CGFloat = 5.0 + + let textTopInset: CGFloat = 9.0 + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let titleColor = component.titleColor + let textColor = component.textColor + let linkColor = component.iconColor + let titleMarkdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor), + link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== linkColor { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, linkColor) + } + + let titleAttributedString = parseMarkdownIntoAttributedString(component.title, attributes: titleMarkdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = titleAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { + titleAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: titleAttributedString.string)) + } + + let title = title.update( + component: MultilineTextComponent( + text: .plain(titleAttributedString), + horizontalAlignment: .center, + maximumNumberOfLines: 1, + highlightColor: linkColor.withAlphaComponent(0.1), + highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + component.action?() + } + } + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + + let textMarkdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: component.text, attributes: textMarkdownAttributes), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: linkColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + component.action?() + } + } + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), + transition: .immediate + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: component.iconColor + ), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + + context.add(title + .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) + ) + + context.add(text + .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) + ) + + context.add(icon + .position(CGPoint(x: 47.0, y: textTopInset + 18.0)) + ) + + return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 18.0) + } + } +} diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 2bb095f7f4..8dc211102b 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -28,6 +28,8 @@ private let productIdentifiers = [ "org.telegram.telegramPremium.sixMonths.code_x10", "org.telegram.telegramPremium.twelveMonths.code_x10", + "org.telegram.telegramPremium.oneWeek.auth", + "org.telegram.telegramStars.topup.x15", "org.telegram.telegramStars.topup.x25", "org.telegram.telegramStars.topup.x50", diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 9cad24de43..6254850f9e 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -120,6 +120,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiActionIconComponent", "//submodules/TelegramUI/Components/ScrollComponent", "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/BusinessPageComponent.swift b/submodules/PremiumUI/Sources/BusinessPageComponent.swift index 72ae9e5639..46c469d7ae 100644 --- a/submodules/PremiumUI/Sources/BusinessPageComponent.swift +++ b/submodules/PremiumUI/Sources/BusinessPageComponent.swift @@ -11,6 +11,7 @@ import Markdown import TelegramPresentationData import BundleIconComponent import ScrollComponent +import PremiumCoinComponent private final class HeaderComponent: Component { let context: AccountContext diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 7d0d43370a..61175ca87b 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -34,6 +34,7 @@ import EntityKeyboard import EmojiActionIconComponent import ScrollComponent import PremiumStarComponent +import PremiumCoinComponent public enum PremiumSource: Equatable { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { @@ -1403,7 +1404,7 @@ final class PerkComponent: CombinedComponent { private final class PremiumIntroScreenContentComponent: CombinedComponent { typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) - let context: AccountContext + let screenContext: PremiumIntroScreen.ScreenContext let mode: PremiumIntroScreen.Mode let source: PremiumSource let forceDark: Bool @@ -1423,7 +1424,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let shareLink: (String) -> Void init( - context: AccountContext, + screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, @@ -1442,7 +1443,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void ) { - self.context = context + self.screenContext = screenContext self.mode = mode self.source = source self.forceDark = forceDark @@ -1463,9 +1464,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } static func ==(lhs: PremiumIntroScreenContentComponent, rhs: PremiumIntroScreenContentComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } if lhs.source != rhs.source { return false } @@ -1498,7 +1496,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } final class State: ComponentState { - private let context: AccountContext + private let screenContext: PremiumIntroScreen.ScreenContext private let present: (ViewController) -> Void var products: [PremiumProduct]? @@ -1542,104 +1540,122 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var cachedChevronImage: (UIImage, PresentationTheme)? init( - context: AccountContext, + screenContext: PremiumIntroScreen.ScreenContext, source: PremiumSource, present: @escaping (ViewController) -> Void ) { - self.context = context + self.screenContext = screenContext self.present = present super.init() - self.disposable = (context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Configuration.App(), - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] appConfiguration, accountPeer in - if let strongSelf = self { - let isFirstTime = strongSelf.peer == nil + let premiumIntroConfiguration: Signal + let accountPeer: Signal + switch screenContext { + case let .accountContext(context): + premiumIntroConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) + |> map { appConfiguration in + return PremiumIntroConfiguration.with(appConfiguration: appConfiguration) + } + accountPeer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + case .sharedContext: + premiumIntroConfiguration = .single(PremiumIntroConfiguration.defaultValue) + accountPeer = .single(nil) + } + + self.disposable = combineLatest( + queue: Queue.mainQueue(), + premiumIntroConfiguration, + accountPeer + ).start(next: { [weak self] premiumIntroConfiguration, accountPeer in + guard let self else { + return + } + let isFirstTime = self.peer == nil + + self.configuration = premiumIntroConfiguration + self.peer = accountPeer + self.updated(transition: .immediate) + + if let identifier = source.identifier, isFirstTime { + var jsonString: String = "{" + jsonString += "\"source\": \"\(identifier)\"," - strongSelf.configuration = PremiumIntroConfiguration.with(appConfiguration: appConfiguration) - strongSelf.peer = accountPeer - strongSelf.updated(transition: .immediate) + jsonString += "\"data\": {\"premium_promo_order\":[" + var isFirst = true + for perk in premiumIntroConfiguration.perks { + if !isFirst { + jsonString += "," + } + isFirst = false + jsonString += "\"\(perk.identifier)\"" + } + jsonString += "]}}" - if let identifier = source.identifier, isFirstTime { - var jsonString: String = "{" - jsonString += "\"source\": \"\(identifier)\"," - - jsonString += "\"data\": {\"premium_promo_order\":[" - var isFirst = true - for perk in strongSelf.configuration.perks { - if !isFirst { - jsonString += "," - } - isFirst = false - jsonString += "\"\(perk.identifier)\"" - } - jsonString += "]}}" - - if let data = jsonString.data(using: .utf8), let json = JSON(data: data) { - addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_show", data: json) - } + if let context = screenContext.context, let data = jsonString.data(using: .utf8), let json = JSON(data: data) { + addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_show", data: json) } } }) - let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() - - let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) - self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey]) - |> deliverOnMainQueue).start(next: { [weak self] views in - guard let strongSelf = self else { - return - } - if let view = views.views[stickersKey] as? OrderedItemListView { - for item in view.items { - if let mediaItem = item.contents.get(RecentMediaItem.self) { - let file = mediaItem.media._parse() - strongSelf.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) - if let effect = file.videoThumbnails.first { - strongSelf.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: effect.resource).start()) + if let context = screenContext.context { + let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() + + let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) + self.stickersDisposable = (context.account.postbox.combinedView(keys: [stickersKey]) + |> deliverOnMainQueue).start(next: { [weak self] views in + guard let self else { + return + } + if let view = views.views[stickersKey] as? OrderedItemListView { + for item in view.items { + if let mediaItem = item.contents.get(RecentMediaItem.self) { + let file = mediaItem.media._parse() + self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) + if let effect = file.videoThumbnails.first { + self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: effect.resource).start()) + } } } } - } - }) - - self.newPerksDisposable = combineLatest(queue: Queue.mainQueue(), - ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessChatbotsBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedBusinessBadge, dismissedBusinessLinksBadge, dismissedBusinessIntroBadge, dismissedBusinessChatbotsBadge in - guard let self else { - return - } - var newPerks: [String] = [] - if !dismissedBusinessBadge { - newPerks.append(PremiumPerk.business.identifier) - } - if !dismissedBusinessLinksBadge { - newPerks.append(PremiumPerk.businessLinks.identifier) - } - if !dismissedBusinessIntroBadge { - newPerks.append(PremiumPerk.businessIntro.identifier) - } - if !dismissedBusinessChatbotsBadge { - newPerks.append(PremiumPerk.businessChatBots.identifier) - } - self.newPerks = newPerks - self.updated() - }) - - self.adsEnabledDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AdsEnabled(id: context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] adsEnabled in - guard let self else { - return - } - self.adsEnabled = adsEnabled - self.updated() - }) + }) + + self.newPerksDisposable = combineLatest( + queue: Queue.mainQueue(), + ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedBusinessChatbotsBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedBusinessBadge, dismissedBusinessLinksBadge, dismissedBusinessIntroBadge, dismissedBusinessChatbotsBadge in + guard let self else { + return + } + var newPerks: [String] = [] + if !dismissedBusinessBadge { + newPerks.append(PremiumPerk.business.identifier) + } + if !dismissedBusinessLinksBadge { + newPerks.append(PremiumPerk.businessLinks.identifier) + } + if !dismissedBusinessIntroBadge { + newPerks.append(PremiumPerk.businessIntro.identifier) + } + if !dismissedBusinessChatbotsBadge { + newPerks.append(PremiumPerk.businessChatBots.identifier) + } + self.newPerks = newPerks + self.updated() + }) + + self.adsEnabledDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AdsEnabled(id: context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] adsEnabled in + guard let self else { + return + } + self.adsEnabled = adsEnabled + self.updated() + }) + } } deinit { @@ -1655,6 +1671,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { private weak var emojiStatusSelectionController: ViewController? private var previousEmojiSetupTimestamp: Double? func openEmojiSetup(sourceView: UIView, currentFileId: Int64?, color: UIColor?) { + guard let context = self.screenContext.context else { + return + } let currentTimestamp = CACurrentMediaTime() if let previousTimestamp = self.previousEmojiSetupTimestamp, currentTimestamp < previousTimestamp + 1.0 { return @@ -1668,20 +1687,20 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } let controller = EmojiStatusSelectionController( - context: self.context, + context: context, mode: .statusSelection, sourceView: sourceView, emojiContent: EmojiPagerContentComponent.emojiInputData( - context: self.context, - animationCache: self.context.animationCache, - animationRenderer: self.context.animationRenderer, + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, isStandalone: false, subject: .status, hasTrending: false, topReactionItems: [], areUnicodeEmojiEnabled: false, areCustomEmojiEnabled: true, - chatPeerId: self.context.account.peerId, + chatPeerId: context.account.peerId, selectedItems: selectedItems, topStatusTitle: nil, backgroundIconColor: color @@ -1701,7 +1720,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, source: self.source, present: self.present) + return State(screenContext: self.screenContext, source: self.source, present: self.present) } static var body: Body { @@ -1733,7 +1752,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let theme = environment.theme let strings = environment.strings - let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 } + let presentationData = context.component.screenContext.presentationData let availableWidth = context.availableSize.width let sideInsets = sideInset * 2.0 + environment.safeInsets.left + environment.safeInsets.right @@ -1786,8 +1805,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } else if case .giftTerms = context.component.source { textString = strings.Premium_PersonalDescription } else if let _ = context.component.otherPeerName { - if case let .gift(fromId, _, _, giftCode) = context.component.source { - if fromId == context.component.context.account.peerId { + if case let .gift(fromId, _, _, giftCode) = context.component.source, let accountContext = context.component.screenContext.context { + if fromId == accountContext.account.peerId { textString = strings.Premium_GiftedDescriptionYou } else { if let giftCode { @@ -1895,7 +1914,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0x3dbd4a) ] - let accountContext = context.component.context + let accountContext = context.component.screenContext.context let present = context.component.present let push = context.component.push let selectProduct = context.component.selectProduct @@ -2068,6 +2087,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { iconName: perk.iconName ))), false), action: { [weak state] _ in + guard let accountContext else { + return + } var demoSubject: PremiumDemoScreen.Subject switch perk { case .doubleLimits: @@ -2239,6 +2261,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { iconName: perk.iconName ))), false), action: { [weak state] _ in + guard let accountContext else { + return + } + let isPremium = state?.isPremium == true if isPremium { switch perk { @@ -2396,46 +2422,48 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let accentColor = environment.theme.list.itemAccentColor var perksItems: [AnyComponentWithIdentity] = [] - perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Business_SetEmojiStatus, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - ))), - AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Business_SetEmojiStatusInfo, - font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)), - textColor: environment.theme.list.itemSecondaryTextColor - )), - maximumNumberOfLines: 0, - lineSpacing: 0.18 - ))) - ], alignment: .left, spacing: 2.0)), - leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(PerkIconComponent( - backgroundColor: UIColor(rgb: 0x676bff), - foregroundColor: .white, - iconName: "Premium/BusinessPerk/Status" - ))), false), - icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( - context: context.component.context, - color: accentColor, - fileId: status?.fileId, - file: nil - )))), - accessory: nil, - action: { [weak state] view in - guard let view = view as? ListActionItemComponent.View, let iconView = view.iconView else { - return + if let accountContext = context.component.screenContext.context { + perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Business_SetEmojiStatus, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Business_SetEmojiStatusInfo, + font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 0, + lineSpacing: 0.18 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(PerkIconComponent( + backgroundColor: UIColor(rgb: 0x676bff), + foregroundColor: .white, + iconName: "Premium/BusinessPerk/Status" + ))), false), + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( + context: accountContext, + color: accentColor, + fileId: status?.fileId, + file: nil + )))), + accessory: nil, + action: { [weak state] view in + guard let view = view as? ListActionItemComponent.View, let iconView = view.iconView else { + return + } + state?.openEmojiSetup(sourceView: iconView, currentFileId: nil, color: accentColor) } - state?.openEmojiSetup(sourceView: iconView, currentFileId: nil, color: accentColor) - } - )))) + )))) + } perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, @@ -2464,6 +2492,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { iconName: "Premium/BusinessPerk/Tag" ))), false), action: { _ in + guard let accountContext else { + return + } push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil)) } )))) @@ -2495,6 +2526,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { iconName: "Premium/Perk/Stories" ))), false), action: { _ in + guard let accountContext else { + return + } push(accountContext.sharedContext.makeMyStoriesController(context: accountContext, isArchive: false)) } )))) @@ -2560,6 +2594,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { ))), ], alignment: .left, spacing: 2.0)), accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: state.adsEnabled, action: { [weak state] value in + guard let accountContext else { + return + } let _ = accountContext.engine.accountData.updateAdMessagesEnabled(enabled: value).startStandalone() state?.updated(transition: .immediate) })), @@ -2576,8 +2613,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } let controller = environment.controller let adsInfoTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { _ in - if let controller = controller() as? PremiumIntroScreen { - controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: environment.strings.Business_AdsInfo_URL, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {}) + if let controller = controller() as? PremiumIntroScreen, let context = controller.context { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: environment.strings.Business_AdsInfo_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) } } let adsSettingsSection = adsSettingsSection.update( @@ -2627,7 +2664,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { layoutPerks() layoutOptions() } else if case let .gift(fromPeerId, _, _, giftCode) = context.component.source { - if let giftCode, fromPeerId != context.component.context.account.peerId, !context.component.justBought { + if let giftCode, let accountContext = context.component.screenContext.context, fromPeerId != accountContext.account.peerId, !context.component.justBought { let link = "https://t.me/giftcode/\(giftCode.slug)" let linkButton = linkButton.update( component: Button( @@ -2718,7 +2755,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var isGiftView = false if case let .gift(fromId, _, _, _) = context.component.source { - if fromId == context.component.context.account.peerId { + if let accountContext = context.component.screenContext.context, fromId == accountContext.account.peerId { isGiftView = true } } @@ -2738,14 +2775,12 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let controller = environment.controller let termsTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { attributes in - if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, - let controller = controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let controller = controller() as? PremiumIntroScreen, let context = controller.context, let navigationController = controller.navigationController as? NavigationController { if url.hasPrefix("https://apps.apple.com/account/subscriptions") { - controller.context.sharedContext.applicationBindings.openSubscriptions() + context.sharedContext.applicationBindings.openSubscriptions() } else if url.hasPrefix("https://") || url.hasPrefix("tg://") { - controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: false, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: navigationController, dismissInput: {}) + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) } else { - let context = controller.context let signal: Signal? switch url { case "terms": @@ -2815,7 +2850,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { private final class PremiumIntroScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment - let context: AccountContext + let screenContext: PremiumIntroScreen.ScreenContext let mode: PremiumIntroScreen.Mode let source: PremiumSource let forceDark: Bool @@ -2827,8 +2862,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let copyLink: (String) -> Void let shareLink: (String) -> Void - init(context: AccountContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) { - self.context = context + init(screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) { + self.screenContext = screenContext self.mode = mode self.source = source self.forceDark = forceDark @@ -2842,9 +2877,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } static func ==(lhs: PremiumIntroScreenComponent, rhs: PremiumIntroScreenComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } if lhs.mode != rhs.mode { return false } @@ -2861,7 +2893,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } final class State: ComponentState { - private let context: AccountContext + private let screenContext: PremiumIntroScreen.ScreenContext private let source: PremiumSource private let updateInProgress: (Bool) -> Void private let present: (ViewController) -> Void @@ -2883,10 +2915,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { var isPremium: Bool? var otherPeerName: String? var justBought = false - - let animationCache: AnimationCache - let animationRenderer: MultiAnimationRenderer - + var emojiFile: TelegramMediaFile? var emojiPackTitle: String? private var emojiFileDisposable: Disposable? @@ -2917,43 +2946,44 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } } - init(context: AccountContext, source: PremiumSource, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) { - self.context = context + init(screenContext: PremiumIntroScreen.ScreenContext, source: PremiumSource, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) { + self.screenContext = screenContext self.source = source self.updateInProgress = updateInProgress self.present = present self.completion = completion - - self.animationCache = context.animationCache - self.animationRenderer = context.animationRenderer - + super.init() - self.validPurchases = context.inAppPurchaseManager?.getReceiptPurchases() ?? [] + self.validPurchases = screenContext.inAppPurchaseManager?.getReceiptPurchases() ?? [] let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> - if let inAppPurchaseManager = context.inAppPurchaseManager { + if let inAppPurchaseManager = screenContext.inAppPurchaseManager { availableProducts = inAppPurchaseManager.availableProducts } else { availableProducts = .single([]) } let otherPeerName: Signal - if case let .gift(fromPeerId, toPeerId, _, _) = source { - let otherPeerId = fromPeerId != context.account.peerId ? fromPeerId : toPeerId - otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: otherPeerId)) - |> map { peer -> String? in - return peer?.compactDisplayTitle - } - } else if case let .profile(peerId) = source { - otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> map { peer -> String? in - return peer?.compactDisplayTitle - } - } else if case let .emojiStatus(peerId, _, _, _) = source { - otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> map { peer -> String? in - return peer?.compactDisplayTitle + if let context = screenContext.context { + if case let .gift(fromPeerId, toPeerId, _, _) = source { + let otherPeerId = fromPeerId != context.account.peerId ? fromPeerId : toPeerId + otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: otherPeerId)) + |> map { peer -> String? in + return peer?.compactDisplayTitle + } + } else if case let .profile(peerId) = source { + otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> map { peer -> String? in + return peer?.compactDisplayTitle + } + } else if case let .emojiStatus(peerId, _, _, _) = source { + otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> map { peer -> String? in + return peer?.compactDisplayTitle + } + } else { + otherPeerName = .single(nil) } } else { otherPeerName = .single(nil) @@ -2963,21 +2993,31 @@ private final class PremiumIntroScreenComponent: CombinedComponent { self.isPremium = true } + let isPremium: Signal + let promoConfiguration: Signal + switch screenContext { + case let .accountContext(context): + isPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + return peer?.isPremium ?? false + } + promoConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.PremiumPromo()) + case .sharedContext: + isPremium = .single(false) + promoConfiguration = .single(PremiumPromoConfiguration.defaultValue) + } + self.disposable = combineLatest( queue: Queue.mainQueue(), availableProducts, - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.PremiumPromo()), - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) - |> map { peer -> Bool in - return peer?.isPremium ?? false - }, + promoConfiguration, + isPremium, otherPeerName ).start(next: { [weak self] availableProducts, promoConfiguration, isPremium, otherPeerName in if let strongSelf = self { strongSelf.promoConfiguration = promoConfiguration let hadProducts = strongSelf.products != nil - var products: [PremiumProduct] = [] for option in promoConfiguration.premiumProductOptions { if let product = availableProducts.first(where: { $0.id == option.storeProductId }), product.isSubscription { @@ -2992,8 +3032,10 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if !hadProducts { strongSelf.selectedProductId = strongSelf.products?.first?.id - for (_, video) in promoConfiguration.videos { - strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: .standalone(resource: video.resource), duration: 3.0).start()) + if let context = screenContext.context { + for (_, video) in promoConfiguration.videos { + strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: .standalone(resource: video.resource), duration: 3.0).start()) + } } } @@ -3007,14 +3049,16 @@ private final class PremiumIntroScreenComponent: CombinedComponent { self.emojiPackTitle = info.title self.updated(transition: .immediate) } else { - self.emojiFileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]) - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - strongSelf.emojiFile = result[emojiFileId] - strongSelf.updated(transition: .immediate) - }) + if let context = screenContext.context { + self.emojiFileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + self.emojiFile = result[emojiFileId] + self.updated(transition: .immediate) + }) + } } } } @@ -3032,12 +3076,17 @@ private final class PremiumIntroScreenComponent: CombinedComponent { return } + let presentationData = self.screenContext.presentationData + if case let .gift(_, _, _, giftCode) = self.source, let giftCode, giftCode.usedDate == nil { + guard let context = self.screenContext.context else { + return + } self.inProgress = true self.updateInProgress(true) self.updated(transition: .immediate) - self.paymentDisposable.set((self.context.engine.payments.applyPremiumGiftCode(slug: giftCode.slug) + self.paymentDisposable.set((context.engine.payments.applyPremiumGiftCode(slug: giftCode.slug) |> deliverOnMainQueue).start(error: { [weak self] error in guard let self else { return @@ -3048,8 +3097,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent { self.updated(transition: .immediate) if case let .waitForExpiration(date) = error { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let dateText = stringForMediumDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat) self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.Premium_Gift_ApplyLink_AlreadyHasPremium_Title, text: presentationData.strings.Premium_Gift_ApplyLink_AlreadyHasPremium_Text(dateText).string, timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, action: { _ in return true })) } @@ -3067,16 +3114,15 @@ private final class PremiumIntroScreenComponent: CombinedComponent { return } - guard let inAppPurchaseManager = self.context.inAppPurchaseManager, + guard let inAppPurchaseManager = self.screenContext.inAppPurchaseManager, let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }) else { return } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil var hasActiveSubsciption = false - if let data = self.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_receipt_check"] { + if let context = self.screenContext.context, let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_receipt_check"] { } else if !self.validPurchases.isEmpty && !isUpgrade { let now = Date() @@ -3089,26 +3135,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if hasActiveSubsciption { let errorText = presentationData.strings.Premium_Purchase_OnlyOneSubscriptionAllowed - let alertController = textAlertController(context: self.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) self.present(alertController) return } - addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept") - + if let context = self.screenContext.context { + addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_accept") + } + self.inProgress = true self.updateInProgress(true) self.updated(transition: .immediate) let purpose: AppStoreTransactionPurpose = isUpgrade ? .upgrade : .subscription - let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose) + + let canPurchasePremium: Signal + switch self.screenContext { + case let .accountContext(context): + canPurchasePremium = context.engine.payments.canPurchasePremium(purpose: purpose) + case let .sharedContext(_, engine, _): + canPurchasePremium = engine.payments.canPurchasePremium(purpose: purpose) + } + let _ = (canPurchasePremium |> deliverOnMainQueue).start(next: { [weak self] available in - if let strongSelf = self { - if available { - strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, purpose: purpose) - |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self, case .purchased = status { - strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId) + guard let self else { + return + } + if available { + self.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, purpose: purpose) + |> deliverOnMainQueue).start(next: { [weak self] status in + if let self, case .purchased = status { + let activation: Signal + if let context = self.screenContext.context { + activation = context.account.postbox.peerView(id: context.account.peerId) |> castError(AssignAppStoreTransactionError.self) |> take(until: { view in if let peer = view.peers[view.peerId], peer.isPremium { @@ -3121,70 +3181,82 @@ private final class PremiumIntroScreenComponent: CombinedComponent { return .never() } |> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout)) - |> deliverOnMainQueue).start(error: { [weak self] _ in - if let strongSelf = self { - strongSelf.inProgress = false - strongSelf.updateInProgress(false) - - strongSelf.updated(transition: .immediate) - - addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") - - let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown - let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - strongSelf.present(alertController) - } - }, completed: { [weak self] in - if let strongSelf = self { - let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start() - strongSelf.inProgress = false - strongSelf.updateInProgress(false) - - strongSelf.isPremium = true - strongSelf.justBought = true - - strongSelf.updated(transition: .easeInOut(duration: 0.25)) - strongSelf.completion() - } - })) + } else { + activation = .complete() } - }, error: { [weak self] error in - if let strongSelf = self { - strongSelf.inProgress = false - strongSelf.updateInProgress(false) - strongSelf.updated(transition: .immediate) - - var errorText: String? - switch error { - case .generic: - errorText = presentationData.strings.Premium_Purchase_ErrorUnknown - case .network: - errorText = presentationData.strings.Premium_Purchase_ErrorNetwork - case .notAllowed: - errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed - case .cantMakePayments: - errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments - case .assignFailed: - errorText = presentationData.strings.Premium_Purchase_ErrorUnknown - case .tryLater: - errorText = presentationData.strings.Premium_Purchase_ErrorUnknown - case .cancelled: - break - } - - if let errorText = errorText { - addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") + + self.activationDisposable.set((activation + |> deliverOnMainQueue).start(error: { [weak self] _ in + if let self { + self.inProgress = false + self.updateInProgress(false) - let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - strongSelf.present(alertController) + self.updated(transition: .immediate) + + if let context = self.screenContext.context { + addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail") + } + + let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + self.present(alertController) } + }, completed: { [weak self] in + guard let self else { + return + } + if let context = self.screenContext.context { + let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() + } + self.inProgress = false + self.updateInProgress(false) + + self.isPremium = true + self.justBought = true + + self.updated(transition: .easeInOut(duration: 0.25)) + self.completion() + })) + } + }, error: { [weak self] error in + guard let self else { + return + } + self.inProgress = false + self.updateInProgress(false) + self.updated(transition: .immediate) + + var errorText: String? + switch error { + case .generic: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .network: + errorText = presentationData.strings.Premium_Purchase_ErrorNetwork + case .notAllowed: + errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed + case .cantMakePayments: + errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments + case .assignFailed: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .cancelled: + break + } + + if let errorText = errorText { + if let context = self.screenContext.context { + addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail") } - })) - } else { - strongSelf.inProgress = false - strongSelf.updateInProgress(false) - strongSelf.updated(transition: .immediate) - } + + let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + self.present(alertController) + } + })) + } else { + self.inProgress = false + self.updateInProgress(false) + self.updated(transition: .immediate) } }) } @@ -3201,7 +3273,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, source: self.source, forceHasPremium: self.forceHasPremium, updateInProgress: self.updateInProgress, present: self.present, completion: self.completion) + return State(screenContext: self.screenContext, source: self.source, forceHasPremium: self.forceHasPremium, updateInProgress: self.updateInProgress, present: self.present, completion: self.completion) } static var body: Body { @@ -3248,12 +3320,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent { availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition ) - } else if case let .emojiStatus(_, fileId, _, _) = context.component.source { + } else if case let .emojiStatus(_, fileId, _, _) = context.component.source, case let .accountContext(accountContext) = context.component.screenContext { header = emoji.update( component: EmojiHeaderComponent( - context: context.component.context, - animationCache: state.animationCache, - animationRenderer: state.animationRenderer, + context: accountContext, + animationCache: accountContext.animationCache, + animationRenderer: accountContext.animationRenderer, placeholderColor: environment.theme.list.mediaPlaceholderColor, accentColor: environment.theme.list.itemAccentColor, fileId: fileId, @@ -3356,7 +3428,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } else if case .profile = context.component.source { secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string } else if case let .gift(fromPeerId, _, duration, _) = context.component.source { - if fromPeerId == context.component.context.account.peerId { + if case let .accountContext(accountContext) = context.component.screenContext, fromPeerId == accountContext.account.peerId { if duration == 12 { secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string } else if duration == 6 { @@ -3409,14 +3481,13 @@ private final class PremiumIntroScreenComponent: CombinedComponent { secondaryAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range) } } - let accountContext = context.component.context + let presentController = context.component.present - let secondaryTitle = secondaryTitle.update( component: MultilineTextWithEntitiesComponent( - context: context.component.context, - animationCache: context.state.animationCache, - animationRenderer: context.state.animationRenderer, + context: context.component.screenContext.context, + animationCache: context.component.screenContext.context?.animationCache, + animationRenderer: context.component.screenContext.context?.animationRenderer, placeholderColor: environment.theme.list.mediaPlaceholderColor, text: .plain(secondaryAttributedText), horizontalAlignment: .center, @@ -3431,7 +3502,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } } : nil, tapAction: { [weak state, weak environment] _, _ in - if let emojiFile = state?.emojiFile, let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { + if let emojiFile = state?.emojiFile, let controller = environment?.controller() as? PremiumIntroScreen, let context = controller.context, let navigationController = controller.navigationController as? NavigationController { for attribute in emojiFile.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { var loadedPack: LoadedStickerPack? @@ -3439,7 +3510,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { loadedPack = .result(info: info, items: items, installed: updatedInstalled ?? installed) } - let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in + let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in return false }, actionPerformed: { added in updatedInstalled = added @@ -3462,7 +3533,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let scrollContent = scrollContent.update( component: ScrollComponent( content: AnyComponent(PremiumIntroScreenContentComponent( - context: context.component.context, + screenContext: context.component.screenContext, mode: context.component.mode, source: context.component.source, forceDark: context.component.forceDark, @@ -3570,8 +3641,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { ) var isUnusedGift = false - if case let .gift(fromId, _, _, giftCode) = context.component.source { - if let giftCode, giftCode.usedDate == nil, fromId != context.component.context.account.peerId { + if case let .gift(fromId, _, _, giftCode) = context.component.source, let accountContext = context.component.screenContext.context { + if let giftCode, giftCode.usedDate == nil, fromId != accountContext.account.peerId { isUnusedGift = true } } @@ -3691,12 +3762,70 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } public final class PremiumIntroScreen: ViewControllerComponentContainer { + public enum ScreenContext { + case accountContext(AccountContext) + case sharedContext(SharedAccountContext, TelegramEngineUnauthorized, InAppPurchaseManager) + + var context: AccountContext? { + switch self { + case let .accountContext(context): + return context + case .sharedContext: + return nil + } + } + + var sharedContext: SharedAccountContext { + switch self { + case let .accountContext(context): + return context.sharedContext + case let .sharedContext(sharedContext, _, _): + return sharedContext + } + } + + var inAppPurchaseManager: InAppPurchaseManager? { + switch self { + case let .accountContext(context): + return context.inAppPurchaseManager + case let .sharedContext(_, _, inAppPurchaseManager): + return inAppPurchaseManager + } + } + + var presentationData: PresentationData { + switch self { + case let .accountContext(context): + return context.sharedContext.currentPresentationData.with { $0 } + case let .sharedContext(sharedContext, _, _): + return sharedContext.currentPresentationData.with { $0 } + } + } + + var updatedPresentationData: (initial: PresentationData, signal: Signal) { + switch self { + case let .accountContext(context): + return (initial: context.sharedContext.currentPresentationData.with { $0 }, signal: context.sharedContext.presentationData) + case let .sharedContext(sharedContext, _, _): + return (initial: sharedContext.currentPresentationData.with { $0 }, signal: sharedContext.presentationData) + } + } + } + public enum Mode { case premium case business } - fileprivate let context: AccountContext + fileprivate var context: AccountContext? { + switch self.screenContext { + case let .accountContext(context): + return context + case .sharedContext: + return nil + } + } + private let screenContext: ScreenContext fileprivate let mode: Mode private var didSetReady = false @@ -3709,18 +3838,24 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { public weak var containerView: UIView? public var animationColor: UIColor? - public init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) { - self.context = context + public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) { + self.init(screenContext: .accountContext(context), mode: mode, source: source, modal: modal, forceDark: forceDark, forceHasPremium: forceHasPremium) + } + + public init(screenContext: ScreenContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) { + self.screenContext = screenContext self.mode = mode - + + let presentationData = screenContext.presentationData + var updateInProgressImpl: ((Bool) -> Void)? var pushImpl: ((ViewController) -> Void)? var presentImpl: ((ViewController) -> Void)? var completionImpl: (() -> Void)? var copyLinkImpl: ((String) -> Void)? var shareLinkImpl: ((String) -> Void)? - super.init(context: context, component: PremiumIntroScreenComponent( - context: context, + super.init(component: PremiumIntroScreenComponent( + screenContext: screenContext, mode: mode, source: source, forceDark: forceDark, @@ -3743,10 +3878,8 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { shareLink: { link in shareLinkImpl?(link) } - ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - + ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData) + if modal { let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.setLeftBarButton(cancelItem, animated: false) @@ -3756,11 +3889,12 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { } updateInProgressImpl = { [weak self] inProgress in - if let strongSelf = self { - strongSelf.navigationItem.leftBarButtonItem?.isEnabled = !inProgress - strongSelf.view.disablesInteractiveTransitionGestureRecognizer = inProgress - strongSelf.view.disablesInteractiveModalDismiss = inProgress + guard let self else { + return } + self.navigationItem.leftBarButtonItem?.isEnabled = !inProgress + self.view.disablesInteractiveTransitionGestureRecognizer = inProgress + self.view.disablesInteractiveModalDismiss = inProgress } presentImpl = { [weak self] c in @@ -3789,12 +3923,11 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { } self.dismissAllTooltips() - let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, position: .top, action: { _ in return true }), in: .current) } shareLinkImpl = { [weak self] link in - guard let self, let navigationController = self.navigationController as? NavigationController else { + guard let self, case let .accountContext(context) = screenContext, let navigationController = self.navigationController as? NavigationController else { return } @@ -3807,7 +3940,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { HapticFeedback().success() } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } (navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: peer.id == context.account.peerId ? presentationData.strings.GiftLink_LinkSharedToSavedMessages : presentationData.strings.GiftLink_LinkSharedToChat(peer.compactDisplayTitle).string), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root)) let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: messages) @@ -3820,7 +3952,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { navigationController.pushViewController(peerSelectionController) } - if case .business = mode { + if case .business = mode, case let .accountContext(context) = screenContext { context.account.viewTracker.keepQuickRepliesApproximatelyUpdated() context.account.viewTracker.keepBusinessLinksApproximatelyUpdated() } diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index bbf34ff84e..e686644d5c 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -16,6 +16,7 @@ import BundleIconComponent import Markdown import SolidRoundedButtonNode import BlurredBackgroundComponent +import PremiumCoinComponent public class PremiumLimitsListScreen: ViewController { final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index ddc57bf86d..4d7c541c9a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1220,7 +1220,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) } dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) } - dict[304435204] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) } + dict[-674301568] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) } dict[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) } dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index a223f6f7dc..fe6078fe6d 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -567,7 +567,7 @@ public extension Api.auth { public extension Api.auth { enum SentCode: TypeConstructorDescription { case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) - case sentCodePaymentRequired(storeProduct: String) + case sentCodePaymentRequired(storeProduct: String, phoneCodeHash: String) case sentCodeSuccess(authorization: Api.auth.Authorization) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -582,11 +582,12 @@ public extension Api.auth { if Int(flags) & Int(1 << 1) != 0 {nextType!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} break - case .sentCodePaymentRequired(let storeProduct): + case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash): if boxed { - buffer.appendInt32(304435204) + buffer.appendInt32(-674301568) } serializeString(storeProduct, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) break case .sentCodeSuccess(let authorization): if boxed { @@ -601,8 +602,8 @@ public extension Api.auth { switch self { case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): return ("sentCode", [("flags", flags as Any), ("type", type as Any), ("phoneCodeHash", phoneCodeHash as Any), ("nextType", nextType as Any), ("timeout", timeout as Any)]) - case .sentCodePaymentRequired(let storeProduct): - return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any)]) + case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash): + return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any), ("phoneCodeHash", phoneCodeHash as Any)]) case .sentCodeSuccess(let authorization): return ("sentCodeSuccess", [("authorization", authorization as Any)]) } @@ -638,9 +639,12 @@ public extension Api.auth { public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? { var _1: String? _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!) } else { return nil diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index d1b1b90e71..3175e1573d 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -73,13 +73,13 @@ public class UnauthorizedAccount { public let testingEnvironment: Bool public let postbox: Postbox public let network: Network - private let stateManager: UnauthorizedAccountStateManager + let stateManager: UnauthorizedAccountStateManager private let updateLoginTokenPipe = ValuePipe() public var updateLoginTokenEvents: Signal { return self.updateLoginTokenPipe.signal() } - + private let serviceNotificationPipe = ValuePipe() public var serviceNotificationEvents: Signal { return self.serviceNotificationPipe.signal() @@ -91,7 +91,7 @@ public class UnauthorizedAccount { public let shouldBeServiceTaskMaster = Promise() - init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) { + init(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) { self.networkArguments = networkArguments self.id = id self.rootPath = rootPath @@ -101,11 +101,84 @@ public class UnauthorizedAccount { self.network = network let updateLoginTokenPipe = self.updateLoginTokenPipe let serviceNotificationPipe = self.serviceNotificationPipe - self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: { - updateLoginTokenPipe.putNext(Void()) - }, displayServiceNotification: { text in - serviceNotificationPipe.putNext(text) - }) + let masterDatacenterId = Int32(network.mtProto.datacenterId) + + var updateSentCodeImpl: ((Api.auth.SentCode) -> Void)? + self.stateManager = UnauthorizedAccountStateManager( + network: network, + updateLoginToken: { + updateLoginTokenPipe.putNext(Void()) + }, + updateSentCode: { sentCode in + updateSentCodeImpl?(sentCode) + }, + displayServiceNotification: { text in + serviceNotificationPipe.putNext(text) + } + ) + + updateSentCodeImpl = { [weak self] sentCode in + switch sentCode { + case .sentCodePaymentRequired: + break + case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout): + let _ = postbox.transaction({ transaction in + var parsedNextType: AuthorizationCodeNextType? + if let nextType = nextType { + parsedNextType = AuthorizationCodeNextType(apiType: nextType) + } + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, syncContacts) = state.contents { + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) + } + }).start() + case let .sentCodeSuccess(authorization): + switch authorization { + case let .authorization(_, _, _, futureAuthToken, user): + let _ = postbox.transaction({ [weak self] transaction in + var syncContacts = true + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, syncContactsValue) = state.contents { + syncContacts = syncContactsValue + } + + if let futureAuthToken = futureAuthToken { + storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData()) + } + + let user = TelegramUser(user: user) + var isSupportUser = false + if let phone = user.phone, phone.hasPrefix("42"), phone.count <= 5 { + isSupportUser = true + } + let state = AuthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: []) + initializedAppSettingsAfterLogin(transaction: transaction, appVersion: networkArguments.appVersion, syncContacts: syncContacts) + transaction.setState(state) + return accountManager.transaction { [weak self] transaction -> SendAuthorizationCodeResult in + if let self { + switchToAuthorizedAccount(transaction: transaction, account: self, isSupportUser: isSupportUser) + } + return .loggedIn + } + }).start() + case let .authorizationSignUpRequired(_, termsOfService): + let _ = postbox.transaction({ [weak self] transaction in + if let self { + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, syncContacts) = state.contents { + let _ = beginSignUp( + account: self, + data: AuthorizationSignUpData( + number: number, + codeHash: codeHash, + code: .phoneCode(""), + termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)), + syncContacts: syncContacts + ) + ).start() + } + } + }).start() + } + } + } network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get() |> map { mode -> Bool in @@ -152,7 +225,7 @@ public class UnauthorizedAccount { |> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal in return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration) |> map { network in - let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) + let updated = UnauthorizedAccount(accountManager: accountManager, networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) return updated } @@ -250,7 +323,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in - return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) + return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } case let authorizedState as AuthorizedAccountState: return postbox.transaction { transaction -> String? in @@ -269,7 +342,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in - return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) + return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } } } diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 40a363f57c..836687ce7f 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -356,9 +356,8 @@ public func sendAuthorizationCode(accountManager: AccountManager Void + private let updateSentCode: (Api.auth.SentCode) -> Void private let displayServiceNotification: (String) -> Void - init(network: Network, updateLoginToken: @escaping () -> Void, displayServiceNotification: @escaping (String) -> Void) { + init( + network: Network, + updateLoginToken: @escaping () -> Void, + updateSentCode: @escaping (Api.auth.SentCode) -> Void, + displayServiceNotification: @escaping (String) -> Void + ) { self.network = network self.updateLoginToken = updateLoginToken + self.updateSentCode = updateSentCode self.displayServiceNotification = displayServiceNotification } @@ -65,11 +72,18 @@ final class UnauthorizedAccountStateManager { self.updateServiceDisposable.dispose() } + func addUpdates(_ updates: Api.Updates) { + self.queue.async { + self.updateService?.addUpdates(updates) + } + } + func reset() { self.queue.async { if self.updateService == nil { self.updateService = UnauthorizedUpdateMessageService() let updateLoginToken = self.updateLoginToken + let updateSentCode = self.updateSentCode let displayServiceNotification = self.displayServiceNotification self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in for update in updates { @@ -81,6 +95,8 @@ final class UnauthorizedAccountStateManager { if popup { displayServiceNotification(message) } + case let .updateSentPhoneCode(sentCode): + updateSentCode(sentCode) default: break } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift index 947985dc24..20b3ca184b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift @@ -182,7 +182,7 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool) case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool) case signUp(number: String, codeHash: String, firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, syncContacts: Bool) - case payment(number: String, codeHash: String, storeProduct: String) + case payment(number: String, codeHash: String, storeProduct: String, syncContacts: Bool) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { @@ -216,9 +216,8 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable self = .awaitingAccountReset(protectedUntil: decoder.decodeInt32ForKey("protectedUntil", orElse: 0), number: decoder.decodeOptionalStringForKey("number"), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) case UnauthorizedAccountStateContentsValue.signUp.rawValue: self = .signUp(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), firstName: decoder.decodeStringForKey("f", orElse: ""), lastName: decoder.decodeStringForKey("l", orElse: ""), termsOfService: decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) - case UnauthorizedAccountStateContentsValue.payment.rawValue: - self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: "")) + self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) default: assertionFailure() self = .empty @@ -308,11 +307,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable encoder.encodeNil(forKey: "tos") } encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") - case let .payment(number, codeHash, storeProduct): + case let .payment(number, codeHash, storeProduct, syncContacts): encoder.encodeInt32(UnauthorizedAccountStateContentsValue.payment.rawValue, forKey: "v") encoder.encodeString(number, forKey: "n") encoder.encodeString(codeHash, forKey: "h") encoder.encodeString(storeProduct, forKey: "storeProduct") + encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") } } @@ -384,8 +384,8 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable } else { return false } - case let .payment(number, codeHash, storeProduct): - if case .payment(number, codeHash, storeProduct) = rhs { + case let .payment(number, codeHash, storeProduct, syncContacts): + if case .payment(number, codeHash, storeProduct, syncContacts) = rhs { return true } else { return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift index 68f0cf550a..c89af92e93 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift @@ -148,7 +148,7 @@ private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTran } } -func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager?, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal { +func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal { return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose) |> castError(AssignAppStoreTransactionError.self) |> mapToSignal { purpose -> Signal in @@ -161,7 +161,26 @@ func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateMana } } |> mapToSignal { updates -> Signal in - stateManager?.addUpdates(updates) + stateManager.addUpdates(updates) + return .complete() + } + } +} + +func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: UnauthorizedAccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal { + return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose) + |> castError(AssignAppStoreTransactionError.self) + |> mapToSignal { purpose -> Signal in + return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose)) + |> mapError { error -> AssignAppStoreTransactionError in + if error.errorCode == 406 { + return .serverProvided + } else { + return .generic + } + } + |> mapToSignal { updates -> Signal in + stateManager.addUpdates(updates) return .complete() } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index c26d11bc60..7c1a697456 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -164,7 +164,7 @@ public extension TelegramEngineUnauthorized { } public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal { - return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: nil, receipt: receipt, purpose: purpose) + return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index be901426e8..22ee8094e3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -1213,9 +1213,9 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE private var currentTheme: PresentationTheme? private var currentStrings: PresentationStrings? - private let stars: StarsAmount? + private let stars: Int64? - public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: StarsAmount?) { + public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: Int64?) { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled self.stars = stars @@ -1295,7 +1295,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE } ) if let amount = self.stars { - let starsString = presentationStringsFormattedNumber(Int32(amount.value), interfaceState.dateTimeFormat.groupingSeparator) + let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator) let rawText: String if self.isPremiumDisabled { rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string @@ -1426,7 +1426,7 @@ private enum ChatEmptyNodeContentType: Equatable { case greeting case topic case premiumRequired - case starsRequired + case starsRequired(Int64) } private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode { @@ -1815,8 +1815,8 @@ public final class ChatEmptyNode: ASDisplayNode { } else if let _ = interfaceState.peerNearbyData { contentType = .peerNearby } else if let peer = peer as? TelegramUser { - if let _ = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil { - contentType = .starsRequired + if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil { + contentType = .starsRequired(sendPaidMessageStars.value) } else if interfaceState.isPremiumRequiredForMessaging { contentType = .premiumRequired } else { @@ -1881,8 +1881,8 @@ public final class ChatEmptyNode: ASDisplayNode { node = ChatEmptyNodeTopicChatContent(context: self.context) case .premiumRequired: node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil) - case .starsRequired: - node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: interfaceState.sendPaidMessageStars) + case let .starsRequired(stars): + node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars) } self.content = (contentType, node) self.addSubnode(node) @@ -1893,7 +1893,12 @@ public final class ChatEmptyNode: ASDisplayNode { node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction) } } - self.isUserInteractionEnabled = [.peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud].contains(contentType) + switch contentType { + case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud: + self.isUserInteractionEnabled = true + default: + self.isUserInteractionEnabled = false + } let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 1064b9d35a..d967016c40 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1044,6 +1044,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -1057,6 +1058,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -1086,6 +1089,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: messageEffect, replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.message), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 297d1a9980..2df108a4e8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -675,6 +675,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message) if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview { dateReactionsAndPeers = ([], []) @@ -688,6 +689,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -747,6 +750,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId), messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: message), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index eee6a16b63..6eb157593b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -130,7 +130,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) needReactions = false break outer - } else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo { + } else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo, message.id.peerId.namespace == Namespaces.Peer.CloudUser { result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) addedPriceInfo = true } @@ -2276,6 +2276,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: message) if message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -2289,6 +2290,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -2337,6 +2340,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: message), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index e6156462fc..a64006553e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -233,6 +233,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -246,6 +247,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -300,6 +303,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: messageEffect, replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index 7665ed8d47..eca913ad51 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -195,6 +195,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var areReactionsTags: Bool var messageEffect: AvailableMessageEffects.MessageEffect? var replyCount: Int + var starsCount: Int64? var isPinned: Bool var hasAutoremove: Bool var canViewReactionList: Bool @@ -218,6 +219,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { areReactionsTags: Bool, messageEffect: AvailableMessageEffects.MessageEffect?, replyCount: Int, + starsCount: Int64?, isPinned: Bool, hasAutoremove: Bool, canViewReactionList: Bool, @@ -240,6 +242,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { self.areReactionsTags = areReactionsTags self.messageEffect = messageEffect self.replyCount = replyCount + self.starsCount = starsCount self.isPinned = isPinned self.hasAutoremove = hasAutoremove self.canViewReactionList = canViewReactionList @@ -262,6 +265,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { private var repliesIcon: ASImageNode? private var selfExpiringIcon: ASImageNode? private var replyCountNode: TextNode? + private var starsIcon: ASImageNode? + private var starsCountNode: TextNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? @@ -316,11 +321,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var currentBackgroundNode = self.backgroundNode var currentImpressionIcon = self.impressionIcon var currentRepliesIcon = self.repliesIcon + var currentStarsIcon = self.starsIcon let currentType = self.type let currentTheme = self.theme let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) + let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode) let reactionButtonsContainer = self.reactionButtonsContainer @@ -337,6 +344,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { let clockMinImage: UIImage? var impressionImage: UIImage? var repliesImage: UIImage? + var starsImage: UIImage? let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType @@ -404,6 +412,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.incomingDateAndStatusPinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.incomingDateAndStatusRepliesIcon + } case let .BubbleOutgoing(status): dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor outgoingStatus = status @@ -420,6 +431,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.outgoingDateAndStatusPinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.outgoingDateAndStatusRepliesIcon + } case .ImageIncoming: dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor backgroundImage = graphics.dateAndStatusMediaBackground @@ -436,6 +450,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.mediaPinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.mediaRepliesIcon + } case let .ImageOutgoing(status): dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor outgoingStatus = status @@ -453,6 +470,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.mediaPinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.mediaRepliesIcon + } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -471,6 +491,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.freePinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.freeRepliesIcon + } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -489,6 +512,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.freePinnedIcon } + if (arguments.starsCount ?? 0) != 0 { + starsImage = graphics.freeRepliesIcon + } } var updatedDateText = arguments.dateText @@ -541,6 +567,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { currentRepliesIcon = nil } + var starsIconSize = CGSize() + if let starsImage = starsImage { + if currentStarsIcon == nil { + let iconNode = ASImageNode() + iconNode.isLayerBacked = true + iconNode.displayWithoutProcessing = true + iconNode.displaysAsynchronously = false + currentStarsIcon = iconNode + } + starsIconSize = starsImage.size + } else { + currentStarsIcon = nil + } + if let outgoingStatus = outgoingStatus { switch outgoingStatus { case .Sending: @@ -652,6 +692,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? + var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? let reactionSize: CGFloat = 8.0 let reactionSpacing: CGFloat = 2.0 @@ -676,6 +717,21 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += 12.0 } + if let starsCount = arguments.starsCount, starsCount > 0 { + let countString: String + if starsCount > 1000000 { + countString = "\(starsCount / 1000000)M" + } else if starsCount > 1000 { + countString = "\(starsCount / 1000)K" + } else { + countString = "\(starsCount)" + } + + let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) + reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 + starsCountLayoutAndApply = layoutAndApply + } + if arguments.messageEffect != nil { reactionInset += 13.0 } @@ -1237,6 +1293,56 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { replyCountNode.removeFromSupernode() } } + + if let currentStarsIcon = currentStarsIcon { + currentStarsIcon.displaysAsynchronously = false + if currentStarsIcon.image !== starsImage { + currentStarsIcon.image = starsImage + } + if currentStarsIcon.supernode == nil { + strongSelf.starsIcon = currentStarsIcon + strongSelf.addSubnode(currentStarsIcon) + if animation.isAnimated { + currentStarsIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + let starsIconFrame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - starsIconSize.height) / 2.0)), size: starsIconSize) + animation.animator.updateFrame(layer: currentStarsIcon.layer, frame: starsIconFrame, completion: nil) + reactionOffset += 9.0 + } else if let starsIcon = strongSelf.starsIcon { + strongSelf.starsIcon = nil + if animation.isAnimated { + starsIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsIcon] _ in + starsIcon?.removeFromSupernode() + }) + } else { + starsIcon.removeFromSupernode() + } + } + + if let (layout, apply) = starsCountLayoutAndApply { + let node = apply() + if strongSelf.starsCountNode !== node { + strongSelf.starsCountNode?.removeFromSupernode() + strongSelf.addSubnode(node) + strongSelf.starsCountNode = node + if animation.isAnimated { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + let starsCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size) + animation.animator.updateFrame(layer: node.layer, frame: starsCountFrame, completion: nil) + reactionOffset += 4.0 + layout.size.width + } else if let starsCountNode = strongSelf.starsCountNode { + strongSelf.starsCountNode = nil + if animation.isAnimated { + starsCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsCountNode] _ in + starsCountNode?.removeFromSupernode() + }) + } else { + starsCountNode.removeFromSupernode() + } + } } }) }) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift index a2e2b5deef..a41e7a8686 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift @@ -295,6 +295,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode var rawText = "" var rawEntities: [MessageTextEntity] = [] var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -311,6 +312,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -447,6 +450,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index e939dc18e6..263522c344 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -578,6 +578,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: nil, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 5357300699..88c6dd9b03 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -898,6 +898,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: arguments.context.account.peerId, accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage) if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview { dateReactionsAndPeers = ([], []) @@ -911,6 +912,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if let channel = arguments.message.peers[arguments.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, arguments.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } if arguments.forcedIsEdited { @@ -956,6 +959,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId), messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode, hasAutoremove: arguments.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: arguments.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index a55f886df6..f95da1047c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -524,6 +524,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let sentViaBot = false var viewCount: Int? = nil var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -537,6 +538,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -583,6 +586,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: messageEffect, replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 5946804836..468ea63fdf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -83,6 +83,7 @@ public struct ChatMessageDateAndStatus { public var dateReactions: [MessageReaction] public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)] public var dateReplies: Int + public var starsCount: Int64? public var isPinned: Bool public var dateText: String @@ -93,6 +94,7 @@ public struct ChatMessageDateAndStatus { dateReactions: [MessageReaction], dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)], dateReplies: Int, + starsCount: Int64?, isPinned: Bool, dateText: String ) { @@ -102,6 +104,7 @@ public struct ChatMessageDateAndStatus { self.dateReactions = dateReactions self.dateReactionPeers = dateReactionPeers self.dateReplies = dateReplies + self.starsCount = starsCount self.isPinned = isPinned self.dateText = dateText } @@ -1118,6 +1121,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId), messageEffect: messageEffect, replyCount: dateAndStatus.dateReplies, + starsCount: dateAndStatus.starsCount, isPinned: dateAndStatus.isPinned, hasAutoremove: message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: message), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift index 2c8c18860a..a9511b99c1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift @@ -192,6 +192,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -205,6 +206,8 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -284,6 +287,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 49392be769..8f8397d26e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -307,6 +307,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -323,6 +324,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -373,6 +376,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { dateReactions: dateReactionsAndPeers.reactions, dateReactionPeers: dateReactionsAndPeers.peers, dateReplies: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, dateText: dateText ) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 332531f153..0017cedcf4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -1054,6 +1054,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -1067,6 +1068,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -1125,6 +1128,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift index 8d5e633293..01a336ad60 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -56,6 +56,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod var viewCount: Int? var rawText = "" var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -71,6 +72,8 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -140,6 +143,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index c968aa1a50..e2d44addfe 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -602,6 +602,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -615,6 +616,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -648,6 +651,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.message), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index f6e820a9af..1e0dd952d7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -264,6 +264,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var dateReplies = 0 + var starsCount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -278,6 +279,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { dateReplies = Int(attribute.count) } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value } } @@ -647,6 +650,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, + starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.topMessage), diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD index a5ce7cf0f9..7f66e412c3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD @@ -28,7 +28,6 @@ swift_library( "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListItemSliderSelectorComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", - "//submodules/PremiumUI", "//submodules/Components/BlurredBackgroundComponent", "//submodules/Markdown", "//submodules/PresentationDataUtils", @@ -38,6 +37,7 @@ swift_library( "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/ToastComponent", "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index 7a48e4fd5f..c27262b53c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -19,13 +19,13 @@ import ListItemSliderSelectorComponent import ListActionItemComponent import Markdown import BlurredBackgroundComponent -import PremiumUI import PresentationDataUtils import PeerListItemComponent import TelegramStringFormatting import ContextUI import BalancedTextComponent import AlertComponent +import PremiumCoinComponent private func textForTimeout(value: Int32) -> String { if value < 3600 { diff --git a/submodules/TelegramUI/Components/Premium/PremiumCoinComponent/BUILD b/submodules/TelegramUI/Components/Premium/PremiumCoinComponent/BUILD new file mode 100644 index 0000000000..faac7b084f --- /dev/null +++ b/submodules/TelegramUI/Components/Premium/PremiumCoinComponent/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PremiumCoinComponent", + module_name = "PremiumCoinComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/GZip", + "//submodules/LegacyComponents", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumCoinComponent/Sources/PremiumCoinComponent.swift similarity index 99% rename from submodules/PremiumUI/Sources/PremiumCoinComponent.swift rename to submodules/TelegramUI/Components/Premium/PremiumCoinComponent/Sources/PremiumCoinComponent.swift index 3a4a434cb6..866f24c1c5 100644 --- a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumCoinComponent/Sources/PremiumCoinComponent.swift @@ -48,6 +48,9 @@ public final class PremiumCoinComponent: Component { public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { public final class Tag { + public init() { + + } } public func matches(tag: Any) -> Bool { @@ -58,7 +61,7 @@ public final class PremiumCoinComponent: Component { } private var _ready = Promise() - var ready: Signal { + public var ready: Signal { return self._ready.get() } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/Contents.json new file mode 100644 index 0000000000..9d73b22267 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "highprice_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/highprice_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Cost.imageset/highprice_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..68a0449411a0a8d2ba09adf84a4ea0a7c9d03eeb GIT binary patch literal 4940 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/Contents.json new file mode 100644 index 0000000000..d9c134477a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "verificationcode_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/verificationcode_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Authorization/Verification.imageset/verificationcode_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..70e1d19c8cacb2fd9f14b4842f8d8c81d33b03a2 GIT binary patch literal 4908 zcma)Aby$>L(`RW`kdzc%N@-YNkyTWrky=8!VUZL0b5X#IN2m*N7rBC_NeZ6fnsiJU9Nhqiq z4Jp-pC}pJa4I~IQKpmnEgcH(c}VkrfZ(kSzI7alZdBfV(gKuWX2JPe!oNO(NRlt zjU1=>5xdvp69Yc5yjBvg0wt&?+5wmr?PD+6d|aY{F!SeuJM_DMM=*pOP2v03P~Ld_ z@KByvO*D>K*G%^wmt<^H3}@f_H2vyT03`jgh_`S$L7893EU*%Q^aBWzzBD1N#n+f+j`sl(6RCl? zUlJmu2qPFQ@W3qEolJVT1X7VOQW|m+7Lqc+1mP8lHW*ZsVEDm(sipLgK1d#UHF=l> zb)RwHO?rZ8Q^rCPrsh~d>V0p#Ss3=pRr1`H)ma1VEdxUBmfTs(LsoaZYu?$LidaSh z82bz0L!vebuo|x>h#Pn%kSaqoV_Pjd&sr0&fNa>W+Ak{ehT8Rh9g!=_a>9Kdt?usv zMd(t(XZ-o4h2}#FIhmt^1~OC4*vDRmdJcb!sA*7ZU^Xle%m7@T*@mPn3>8axrfs%)7T1MY|t$Lb~_5>!NOUGIhFj z7RMUjH0Jiw`W}1o68Aw9%qP21ZR{m1!(PQy-c&ELz^aHoFFsQz539bFIA2Mn(go|% z56*q@3azvfHNbgAW;m-f&)|!wHQAmJCiH1GL;qNrv>ssnrk;S^LtTY&EvOPyze@6` zwp5cK+bpkHOF@fE9hxPSw~g@mjQgD6grJ62gm>9^(+HDMP{VBuF)mlbNbE&38h+5{ zQ?M)63z=ZUlvU>+JMQuBwS0*izDYMgds|?UzsDGBIfqB4)1t|yV_w$|4ohMjOw>!P zPeddVK?D#g*7;UdHXPP5Rv0T~D~)o)a)Jqt3A!@lzL=G3yB4ktbxHTKvoaQP+4}Pb zR)!P$Ap?dPNm-M5-{ha?bqs|J^7O9`Bn^y?7#B1=XfQhS0W^6dB24TX(>rGCG9pbY z=PtL9JMjB7@%d);=OYh6sOHa5=f{(k>n@In4T!gl$N;n&HPz^zN0ldHE{M=YGiF>732 zL}y{&rvZ!J5#HP0xr$10L9u0g0ddCy)9-fYg#6&ma6TVU1aqaprdos=%(bt$yQJGM zR-4z0)|R=JrShgfW9f}Wi_RKYZ(nBL+?947cAo0y#g@#LFU>Q7ZUJV1=WJKK~e7lP&VDeJ=0Nls;6Dt zJW>r(IZ;-DJK=11`tK0jadY9Dz}SzDx*L7p!+qsD;M*H8)x3IN=M1*rvbIq|I z6L?^9Kzu;Dfths6)G5IlZY|eme@j$c<~ZU&9Tbmy6%AGFb^_)BT>!**ROyJ{G10-* z6N6OPJ=mOXzfQhd)vN7OmYIz2EgOMM;a1nU#I{%RW|!-@KNWnoZuv4`K4zZKkvi-@ zynE7lQu11>t|EQE+hNXOC3u#48gm(gxd~CB9d6l+Iu6$8&qbas?rGTIWn~noWtYr~ zi19&B=a=HshDTtJ4x&?u7=b{zHdK!lnH;@fFM`mmAiSC51+WRzT zdG7F&FLC#$&uXDpz>s~tUDy5M=3CaN@P=}aNeHvZ>7n8KnuLR+>qGaF4?9oBcDPOk zcM=MFD|_L_O7&$PWQWK0qHyQ^JLG0f`4;anm~Rf#HaD4t_nl!isUKMKsj#%%^Zl`^7E ziVrMW+00XWKGuWrakOz8<1)v`6AH%11v+_QEjZ|h-wDEq``LhSq{UB0crIPAPN2|l z`uT%C{(^;n!}=fr1t}>>Qzy6u=z^=NgA6a+{4f^zz3YOzN}+5~jvDr+=I|d>RtgNd z@S=WBLob-v}K`CSoo&XKg2a^NKRr{9Nh^>Y)EgCmZ z0cYttX)IB&DrcjhOTpMss9)s3yRZA(rwt2C^{*Sk(*o}*>(lL&90lGA2CksI^nsqo8tq<%fU^7zwzY|`dP$@tMO|5oKy@u`i%T`P)75Xf zGjW(^ZlW!1e8c>}NtKE zUTi(>V+7^fwj7QD;2?{2s=;L$eys*iQi=c{;t>gX|Iq{rd1vK~7DmcC$rvM&Y{E`- zY#o<;lEgd%^_nLk)bb(gpmj`?a`cxR`DHa1t_#=1Drq&8I^^Jh;WGv zb30|i(U8!Yez{2zk_CXfTL&C7!?d&^5PHo97OiNUsvx?ethJXu& z!Tf#!FvVx{NI%OD&PS!MhGkyr){*2Jx@1~ezcGSo@8w>COyWEeN{j|BQcvOvlbFxQ z*5V>f_!k-P>hR{%x&XlSi$P&vuoQR7aICk`d}pp1P@Fv>)Jp43L}{%MRVnbb0>RRy zR1I-(IG+*mQCa;VCpnR9E+ zSLfAUngl3io0#gztv$V0U4YQ)0!=2r`Hqw}E$!Bq<@AY^Nav%%Ckx!jw+ZXPbn+@b zpFjxlP`NVw_JxPnP+#b;@&cQ7MQJT6p7PZ0J?qc|Z%(fm>@MFkpkAe(aRg!GKO7t6 zS=u~P%sd?^k}R2M{=C|7z?NggbU9NpcH_R!k+0PHSM5|;`H8T^Hg8LPMogSzmgS9c za=*&?_HHI$UMz6@rT=sF+Or} zx$|&z)BS0lWHB9n|k{C@#HC=FcC9BWEdejd<%*+uY>y=H^4 z26w4ah13D$P!_4*gBUuk*rh$D=kQDOeYxo*kC`S1B98$IQ#Ck1j}GAz-(GBt$w)d? zu_rytW%h7U#}H^(^{3bw$?I7bn;H=`o|p5#Bkpg?q&v0J(cU1xSF%V%SO3NcMRMhO zu$XSP#o)_oE-F{sCm%M!w+V}~G)h$&VlZ@^V%0e7&xDgD!&|(QJfAl3uS0y&PV<^< zcta_OsN|Tc+LC3bQHrRH=W0TST=KyCftsUa8M8So5v_G(>3uHZZx8*pT~#~7$r*)o zfWcW|Q)Vhz8}G8;^>uJEoY1fbq)8QcO;EVgEnnRV2pIQMEeu&WI~jGuWs)~B9sGX< zHh3P`=e5t0z0E-BU{C?(d{f2*xHIPUY+6km@_;Ws?`NaNw!T~dV{A+}v47Wg-N}{kJeUKo( z;E(l>Ap9HpNBZSS9qxp}IGV$qKo`yEqElSVN~BOoG#q(;;zhUl1;R`n&oPkLPw3xz z(gpO-3`P-QaULq@B7=)&_sb*Z+!p*F@jIO(|K9`g7kSIIh{~;a4rxh u!h-AXatf%UP-xIaseW9IfCj=9ev$W&d6lUn`bQ;1M1^hvIXGlgW&Z_iH=f!6 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 9968f1e98b..65c74b7751 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2366,13 +2366,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { } } - public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController { - var modal = true + private func mapIntroSource(source: PremiumIntroSource) -> PremiumSource { let mappedSource: PremiumSource switch source { case .settings: mappedSource = .settings - modal = false case .stickers: mappedSource = .stickers case .reactions: @@ -2454,7 +2452,25 @@ public final class SharedAccountContextImpl: SharedAccountContext { case .paidMessages: mappedSource = .paidMessages } - let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark) + return mappedSource + } + + public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController { + var modal = true + if case .settings = source { + modal = false + } + let controller = PremiumIntroScreen(context: context, source: self.mapIntroSource(source: source), modal: modal, forceDark: forceDark) + controller.wasDismissed = dismissed + return controller + } + + public func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController { + var modal = true + if case .settings = source { + modal = false + } + let controller = PremiumIntroScreen(screenContext: .sharedContext(sharedContext, engine, inAppPurchaseManager), source: self.mapIntroSource(source: source), modal: modal) controller.wasDismissed = dismissed return controller }