Various improvements

This commit is contained in:
Ilya Laktyushin 2025-03-11 06:49:43 +04:00
parent ebfa8f08a1
commit 80cd8f7b32
46 changed files with 1200 additions and 386 deletions

View File

@ -1053,6 +1053,8 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> 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 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 func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController

View File

@ -43,6 +43,7 @@ swift_library(
"//submodules/MoreButtonNode:MoreButtonNode", "//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/ContextUI:ContextUI", "//submodules/ContextUI:ContextUI",
"//submodules/InAppPurchaseManager", "//submodules/InAppPurchaseManager",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -764,12 +764,11 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
} }
private func paymentController(number: String, phoneCodeHash: String, storeProduct: String) -> AuthorizationSequencePaymentScreen { 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 { guard let self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() 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() let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
return controller return controller
@ -1302,7 +1301,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
} }
controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel)) controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel))
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
case let .payment(number, codeHash, storeProduct): case let .payment(number, codeHash, storeProduct, _):
var controllers: [ViewController] = [] var controllers: [ViewController] = []
if !self.otherAccountPhoneNumbers.1.isEmpty { if !self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController()) controllers.append(self.splashController())

View File

@ -14,15 +14,19 @@ import ViewControllerComponent
import MultilineTextComponent import MultilineTextComponent
import BalancedTextComponent import BalancedTextComponent
import BundleIconComponent import BundleIconComponent
import LottieComponent
import ButtonComponent import ButtonComponent
import TextFormat import TextFormat
import InAppPurchaseManager import InAppPurchaseManager
import ConfettiEffect import ConfettiEffect
import PremiumCoinComponent
import Markdown
import CountrySelectionUI
import AccountContext
final class AuthorizationSequencePaymentScreenComponent: Component { final class AuthorizationSequencePaymentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let sharedContext: SharedAccountContext
let engine: TelegramEngineUnauthorized let engine: TelegramEngineUnauthorized
let inAppPurchaseManager: InAppPurchaseManager let inAppPurchaseManager: InAppPurchaseManager
let presentationData: PresentationData let presentationData: PresentationData
@ -31,6 +35,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let storeProduct: String let storeProduct: String
init( init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized, engine: TelegramEngineUnauthorized,
inAppPurchaseManager: InAppPurchaseManager, inAppPurchaseManager: InAppPurchaseManager,
presentationData: PresentationData, presentationData: PresentationData,
@ -38,6 +43,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
phoneCodeHash: String, phoneCodeHash: String,
storeProduct: String storeProduct: String
) { ) {
self.sharedContext = sharedContext
self.engine = engine self.engine = engine
self.inAppPurchaseManager = inAppPurchaseManager self.inAppPurchaseManager = inAppPurchaseManager
self.presentationData = presentationData self.presentationData = presentationData
@ -93,9 +99,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
self.state?.updated() self.state?.updated()
let (currency, amount) = storeProduct.priceCurrencyAndAmount let (currency, amount) = storeProduct.priceCurrencyAndAmount
let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount) let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount)
let _ = (component.engine.payments.canPurchasePremium(purpose: purpose) let _ = (component.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] available in |> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else { guard let self else {
@ -111,6 +115,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
guard let self, let controller = self.environment?.controller() else { guard let self, let controller = self.environment?.controller() else {
return return
} }
self.inProgress = false
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
var errorText: String? var errorText: String?
@ -156,7 +161,6 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let environment = environment[EnvironmentType.self].value let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment self.environment = environment
self.component = component
self.state = state self.state = state
if self.component == nil { if self.component == nil {
@ -166,29 +170,136 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
return return
} }
self.products = products self.products = products
if !self.isUpdating {
self.state?.updated() self.state?.updated()
}
}) })
} }
self.component = component
if themeUpdated { if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor self.backgroundColor = environment.theme.list.plainBackgroundColor
} }
let animationHeight: CGFloat = 120.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let animationSize = self.animation.update( let animationSize = self.animation.update(
transition: transition, transition: transition,
component: AnyComponent(LottieComponent( component: AnyComponent(PremiumCoinComponent(
content: LottieComponent.AppBundleContent(name: "Coin"), mode: .business,
startingPosition: .begin isIntro: true,
isVisible: true,
hasIdleAnimations: true
)), )),
environment: {}, 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<Empty>] = []
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 let animationView = self.animation.view {
if animationView.superview == nil { if animationView.superview == nil {
self.addSubview(animationView) 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 buttonHeight: CGFloat = 50.0
@ -196,8 +307,13 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
let sideInset: CGFloat = 16.0 let priceString: String
let buttonString = "Sign up for $1" 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 buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: transition, transition: transition,
@ -210,7 +326,12 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
), ),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable(buttonString), 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, isEnabled: true,
displaysProgress: self.inProgress, displaysProgress: self.inProgress,
@ -243,6 +364,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer { public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer {
public init( public init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized, engine: TelegramEngineUnauthorized,
presentationData: PresentationData, presentationData: PresentationData,
inAppPurchaseManager: InAppPurchaseManager, inAppPurchaseManager: InAppPurchaseManager,
@ -252,6 +374,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
back: @escaping () -> Void back: @escaping () -> Void
) { ) {
super.init(component: AuthorizationSequencePaymentScreenComponent( super.init(component: AuthorizationSequencePaymentScreenComponent(
sharedContext: sharedContext,
engine: engine, engine: engine,
inAppPurchaseManager: inAppPurchaseManager, inAppPurchaseManager: inAppPurchaseManager,
presentationData: presentationData, presentationData: presentationData,
@ -260,6 +383,12 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
storeProduct: storeProduct storeProduct: storeProduct
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData))) ), 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) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@ -285,3 +414,183 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
self.dismiss() 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)
}
}
}

View File

@ -28,6 +28,8 @@ private let productIdentifiers = [
"org.telegram.telegramPremium.sixMonths.code_x10", "org.telegram.telegramPremium.sixMonths.code_x10",
"org.telegram.telegramPremium.twelveMonths.code_x10", "org.telegram.telegramPremium.twelveMonths.code_x10",
"org.telegram.telegramPremium.oneWeek.auth",
"org.telegram.telegramStars.topup.x15", "org.telegram.telegramStars.topup.x15",
"org.telegram.telegramStars.topup.x25", "org.telegram.telegramStars.topup.x25",
"org.telegram.telegramStars.topup.x50", "org.telegram.telegramStars.topup.x50",

View File

@ -120,6 +120,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiActionIconComponent", "//submodules/TelegramUI/Components/EmojiActionIconComponent",
"//submodules/TelegramUI/Components/ScrollComponent", "//submodules/TelegramUI/Components/ScrollComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent", "//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -11,6 +11,7 @@ import Markdown
import TelegramPresentationData import TelegramPresentationData
import BundleIconComponent import BundleIconComponent
import ScrollComponent import ScrollComponent
import PremiumCoinComponent
private final class HeaderComponent: Component { private final class HeaderComponent: Component {
let context: AccountContext let context: AccountContext

View File

@ -34,6 +34,7 @@ import EntityKeyboard
import EmojiActionIconComponent import EmojiActionIconComponent
import ScrollComponent import ScrollComponent
import PremiumStarComponent import PremiumStarComponent
import PremiumCoinComponent
public enum PremiumSource: Equatable { public enum PremiumSource: Equatable {
public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
@ -1403,7 +1404,7 @@ final class PerkComponent: CombinedComponent {
private final class PremiumIntroScreenContentComponent: CombinedComponent { private final class PremiumIntroScreenContentComponent: CombinedComponent {
typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment)
let context: AccountContext let screenContext: PremiumIntroScreen.ScreenContext
let mode: PremiumIntroScreen.Mode let mode: PremiumIntroScreen.Mode
let source: PremiumSource let source: PremiumSource
let forceDark: Bool let forceDark: Bool
@ -1423,7 +1424,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let shareLink: (String) -> Void let shareLink: (String) -> Void
init( init(
context: AccountContext, screenContext: PremiumIntroScreen.ScreenContext,
mode: PremiumIntroScreen.Mode, mode: PremiumIntroScreen.Mode,
source: PremiumSource, source: PremiumSource,
forceDark: Bool, forceDark: Bool,
@ -1442,7 +1443,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
copyLink: @escaping (String) -> Void, copyLink: @escaping (String) -> Void,
shareLink: @escaping (String) -> Void shareLink: @escaping (String) -> Void
) { ) {
self.context = context self.screenContext = screenContext
self.mode = mode self.mode = mode
self.source = source self.source = source
self.forceDark = forceDark self.forceDark = forceDark
@ -1463,9 +1464,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
static func ==(lhs: PremiumIntroScreenContentComponent, rhs: PremiumIntroScreenContentComponent) -> Bool { static func ==(lhs: PremiumIntroScreenContentComponent, rhs: PremiumIntroScreenContentComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.source != rhs.source { if lhs.source != rhs.source {
return false return false
} }
@ -1498,7 +1496,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
final class State: ComponentState { final class State: ComponentState {
private let context: AccountContext private let screenContext: PremiumIntroScreen.ScreenContext
private let present: (ViewController) -> Void private let present: (ViewController) -> Void
var products: [PremiumProduct]? var products: [PremiumProduct]?
@ -1542,26 +1540,42 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var cachedChevronImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)?
init( init(
context: AccountContext, screenContext: PremiumIntroScreen.ScreenContext,
source: PremiumSource, source: PremiumSource,
present: @escaping (ViewController) -> Void present: @escaping (ViewController) -> Void
) { ) {
self.context = context self.screenContext = screenContext
self.present = present self.present = present
super.init() super.init()
self.disposable = (context.engine.data.subscribe( let premiumIntroConfiguration: Signal<PremiumIntroConfiguration, NoError>
TelegramEngine.EngineData.Item.Configuration.App(), let accountPeer: Signal<EnginePeer?, NoError>
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) switch screenContext {
) case let .accountContext(context):
|> deliverOnMainQueue).start(next: { [weak self] appConfiguration, accountPeer in premiumIntroConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
if let strongSelf = self { |> map { appConfiguration in
let isFirstTime = strongSelf.peer == nil 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)
}
strongSelf.configuration = PremiumIntroConfiguration.with(appConfiguration: appConfiguration) self.disposable = combineLatest(
strongSelf.peer = accountPeer queue: Queue.mainQueue(),
strongSelf.updated(transition: .immediate) 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 { if let identifier = source.identifier, isFirstTime {
var jsonString: String = "{" var jsonString: String = "{"
@ -1569,7 +1583,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
jsonString += "\"data\": {\"premium_promo_order\":[" jsonString += "\"data\": {\"premium_promo_order\":["
var isFirst = true var isFirst = true
for perk in strongSelf.configuration.perks { for perk in premiumIntroConfiguration.perks {
if !isFirst { if !isFirst {
jsonString += "," jsonString += ","
} }
@ -1578,35 +1592,36 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
jsonString += "]}}" jsonString += "]}}"
if let data = jsonString.data(using: .utf8), let json = JSON(data: data) { if let context = screenContext.context, let data = jsonString.data(using: .utf8), let json = JSON(data: data) {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_show", data: json) addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_show", data: json)
}
} }
} }
}) })
if let context = screenContext.context {
let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers)
self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey]) self.stickersDisposable = (context.account.postbox.combinedView(keys: [stickersKey])
|> deliverOnMainQueue).start(next: { [weak self] views in |> deliverOnMainQueue).start(next: { [weak self] views in
guard let strongSelf = self else { guard let self else {
return return
} }
if let view = views.views[stickersKey] as? OrderedItemListView { if let view = views.views[stickersKey] as? OrderedItemListView {
for item in view.items { for item in view.items {
if let mediaItem = item.contents.get(RecentMediaItem.self) { if let mediaItem = item.contents.get(RecentMediaItem.self) {
let file = mediaItem.media._parse() let file = mediaItem.media._parse()
strongSelf.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
if let effect = file.videoThumbnails.first { if let effect = file.videoThumbnails.first {
strongSelf.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: effect.resource).start()) self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: effect.resource).start())
} }
} }
} }
} }
}) })
self.newPerksDisposable = combineLatest(queue: Queue.mainQueue(), self.newPerksDisposable = combineLatest(
queue: Queue.mainQueue(),
ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager),
@ -1641,6 +1656,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
self.updated() self.updated()
}) })
} }
}
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
@ -1655,6 +1671,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
private weak var emojiStatusSelectionController: ViewController? private weak var emojiStatusSelectionController: ViewController?
private var previousEmojiSetupTimestamp: Double? private var previousEmojiSetupTimestamp: Double?
func openEmojiSetup(sourceView: UIView, currentFileId: Int64?, color: UIColor?) { func openEmojiSetup(sourceView: UIView, currentFileId: Int64?, color: UIColor?) {
guard let context = self.screenContext.context else {
return
}
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
if let previousTimestamp = self.previousEmojiSetupTimestamp, currentTimestamp < previousTimestamp + 1.0 { if let previousTimestamp = self.previousEmojiSetupTimestamp, currentTimestamp < previousTimestamp + 1.0 {
return return
@ -1668,20 +1687,20 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
let controller = EmojiStatusSelectionController( let controller = EmojiStatusSelectionController(
context: self.context, context: context,
mode: .statusSelection, mode: .statusSelection,
sourceView: sourceView, sourceView: sourceView,
emojiContent: EmojiPagerContentComponent.emojiInputData( emojiContent: EmojiPagerContentComponent.emojiInputData(
context: self.context, context: context,
animationCache: self.context.animationCache, animationCache: context.animationCache,
animationRenderer: self.context.animationRenderer, animationRenderer: context.animationRenderer,
isStandalone: false, isStandalone: false,
subject: .status, subject: .status,
hasTrending: false, hasTrending: false,
topReactionItems: [], topReactionItems: [],
areUnicodeEmojiEnabled: false, areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true, areCustomEmojiEnabled: true,
chatPeerId: self.context.account.peerId, chatPeerId: context.account.peerId,
selectedItems: selectedItems, selectedItems: selectedItems,
topStatusTitle: nil, topStatusTitle: nil,
backgroundIconColor: color backgroundIconColor: color
@ -1701,7 +1720,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
func makeState() -> State { 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 { static var body: Body {
@ -1733,7 +1752,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let theme = environment.theme let theme = environment.theme
let strings = environment.strings 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 availableWidth = context.availableSize.width
let sideInsets = sideInset * 2.0 + environment.safeInsets.left + environment.safeInsets.right 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 { } else if case .giftTerms = context.component.source {
textString = strings.Premium_PersonalDescription textString = strings.Premium_PersonalDescription
} else if let _ = context.component.otherPeerName { } else if let _ = context.component.otherPeerName {
if case let .gift(fromId, _, _, giftCode) = context.component.source { if case let .gift(fromId, _, _, giftCode) = context.component.source, let accountContext = context.component.screenContext.context {
if fromId == context.component.context.account.peerId { if fromId == accountContext.account.peerId {
textString = strings.Premium_GiftedDescriptionYou textString = strings.Premium_GiftedDescriptionYou
} else { } else {
if let giftCode { if let giftCode {
@ -1895,7 +1914,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x3dbd4a) UIColor(rgb: 0x3dbd4a)
] ]
let accountContext = context.component.context let accountContext = context.component.screenContext.context
let present = context.component.present let present = context.component.present
let push = context.component.push let push = context.component.push
let selectProduct = context.component.selectProduct let selectProduct = context.component.selectProduct
@ -2068,6 +2087,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
iconName: perk.iconName iconName: perk.iconName
))), false), ))), false),
action: { [weak state] _ in action: { [weak state] _ in
guard let accountContext else {
return
}
var demoSubject: PremiumDemoScreen.Subject var demoSubject: PremiumDemoScreen.Subject
switch perk { switch perk {
case .doubleLimits: case .doubleLimits:
@ -2239,6 +2261,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
iconName: perk.iconName iconName: perk.iconName
))), false), ))), false),
action: { [weak state] _ in action: { [weak state] _ in
guard let accountContext else {
return
}
let isPremium = state?.isPremium == true let isPremium = state?.isPremium == true
if isPremium { if isPremium {
switch perk { switch perk {
@ -2396,6 +2422,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let accentColor = environment.theme.list.itemAccentColor let accentColor = environment.theme.list.itemAccentColor
var perksItems: [AnyComponentWithIdentity<Empty>] = [] var perksItems: [AnyComponentWithIdentity<Empty>] = []
if let accountContext = context.component.screenContext.context {
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
@ -2423,7 +2450,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
iconName: "Premium/BusinessPerk/Status" iconName: "Premium/BusinessPerk/Status"
))), false), ))), false),
icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent(
context: context.component.context, context: accountContext,
color: accentColor, color: accentColor,
fileId: status?.fileId, fileId: status?.fileId,
file: nil file: nil
@ -2436,6 +2463,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
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( perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
@ -2464,6 +2492,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
iconName: "Premium/BusinessPerk/Tag" iconName: "Premium/BusinessPerk/Tag"
))), false), ))), false),
action: { _ in action: { _ in
guard let accountContext else {
return
}
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil)) 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" iconName: "Premium/Perk/Stories"
))), false), ))), false),
action: { _ in action: { _ in
guard let accountContext else {
return
}
push(accountContext.sharedContext.makeMyStoriesController(context: accountContext, isArchive: false)) push(accountContext.sharedContext.makeMyStoriesController(context: accountContext, isArchive: false))
} }
)))) ))))
@ -2560,6 +2594,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
))), ))),
], alignment: .left, spacing: 2.0)), ], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: state.adsEnabled, action: { [weak state] value in 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() let _ = accountContext.engine.accountData.updateAdMessagesEnabled(enabled: value).startStandalone()
state?.updated(transition: .immediate) state?.updated(transition: .immediate)
})), })),
@ -2576,8 +2613,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} }
let controller = environment.controller let controller = environment.controller
let adsInfoTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { _ in let adsInfoTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { _ in
if let controller = controller() as? PremiumIntroScreen { if let controller = controller() as? PremiumIntroScreen, let context = controller.context {
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: {}) context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: environment.strings.Business_AdsInfo_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
} }
} }
let adsSettingsSection = adsSettingsSection.update( let adsSettingsSection = adsSettingsSection.update(
@ -2627,7 +2664,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
layoutPerks() layoutPerks()
layoutOptions() layoutOptions()
} else if case let .gift(fromPeerId, _, _, giftCode) = context.component.source { } 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 link = "https://t.me/giftcode/\(giftCode.slug)"
let linkButton = linkButton.update( let linkButton = linkButton.update(
component: Button( component: Button(
@ -2718,7 +2755,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var isGiftView = false var isGiftView = false
if case let .gift(fromId, _, _, _) = context.component.source { 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 isGiftView = true
} }
} }
@ -2738,14 +2775,12 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let controller = environment.controller let controller = environment.controller
let termsTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { attributes in let termsTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { attributes in
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, 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 {
let controller = controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
if url.hasPrefix("https://apps.apple.com/account/subscriptions") { 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://") { } 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 { } else {
let context = controller.context
let signal: Signal<ResolvedUrl, NoError>? let signal: Signal<ResolvedUrl, NoError>?
switch url { switch url {
case "terms": case "terms":
@ -2815,7 +2850,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
private final class PremiumIntroScreenComponent: CombinedComponent { private final class PremiumIntroScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let screenContext: PremiumIntroScreen.ScreenContext
let mode: PremiumIntroScreen.Mode let mode: PremiumIntroScreen.Mode
let source: PremiumSource let source: PremiumSource
let forceDark: Bool let forceDark: Bool
@ -2827,8 +2862,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let copyLink: (String) -> Void let copyLink: (String) -> Void
let shareLink: (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) { 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.context = context self.screenContext = screenContext
self.mode = mode self.mode = mode
self.source = source self.source = source
self.forceDark = forceDark self.forceDark = forceDark
@ -2842,9 +2877,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
static func ==(lhs: PremiumIntroScreenComponent, rhs: PremiumIntroScreenComponent) -> Bool { static func ==(lhs: PremiumIntroScreenComponent, rhs: PremiumIntroScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.mode != rhs.mode { if lhs.mode != rhs.mode {
return false return false
} }
@ -2861,7 +2893,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
final class State: ComponentState { final class State: ComponentState {
private let context: AccountContext private let screenContext: PremiumIntroScreen.ScreenContext
private let source: PremiumSource private let source: PremiumSource
private let updateInProgress: (Bool) -> Void private let updateInProgress: (Bool) -> Void
private let present: (ViewController) -> Void private let present: (ViewController) -> Void
@ -2884,9 +2916,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var otherPeerName: String? var otherPeerName: String?
var justBought = false var justBought = false
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
var emojiFile: TelegramMediaFile? var emojiFile: TelegramMediaFile?
var emojiPackTitle: String? var emojiPackTitle: String?
private var emojiFileDisposable: Disposable? private var emojiFileDisposable: Disposable?
@ -2917,28 +2946,26 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
} }
init(context: AccountContext, source: PremiumSource, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) { init(screenContext: PremiumIntroScreen.ScreenContext, source: PremiumSource, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
self.context = context self.screenContext = screenContext
self.source = source self.source = source
self.updateInProgress = updateInProgress self.updateInProgress = updateInProgress
self.present = present self.present = present
self.completion = completion self.completion = completion
self.animationCache = context.animationCache
self.animationRenderer = context.animationRenderer
super.init() super.init()
self.validPurchases = context.inAppPurchaseManager?.getReceiptPurchases() ?? [] self.validPurchases = screenContext.inAppPurchaseManager?.getReceiptPurchases() ?? []
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
if let inAppPurchaseManager = context.inAppPurchaseManager { if let inAppPurchaseManager = screenContext.inAppPurchaseManager {
availableProducts = inAppPurchaseManager.availableProducts availableProducts = inAppPurchaseManager.availableProducts
} else { } else {
availableProducts = .single([]) availableProducts = .single([])
} }
let otherPeerName: Signal<String?, NoError> let otherPeerName: Signal<String?, NoError>
if let context = screenContext.context {
if case let .gift(fromPeerId, toPeerId, _, _) = source { if case let .gift(fromPeerId, toPeerId, _, _) = source {
let otherPeerId = fromPeerId != context.account.peerId ? fromPeerId : toPeerId let otherPeerId = fromPeerId != context.account.peerId ? fromPeerId : toPeerId
otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: otherPeerId)) otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: otherPeerId))
@ -2958,26 +2985,39 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} else { } else {
otherPeerName = .single(nil) otherPeerName = .single(nil)
} }
} else {
otherPeerName = .single(nil)
}
if forceHasPremium { if forceHasPremium {
self.isPremium = true self.isPremium = true
} }
let isPremium: Signal<Bool, NoError>
let promoConfiguration: Signal<PremiumPromoConfiguration, NoError>
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( self.disposable = combineLatest(
queue: Queue.mainQueue(), queue: Queue.mainQueue(),
availableProducts, availableProducts,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.PremiumPromo()), promoConfiguration,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) isPremium,
|> map { peer -> Bool in
return peer?.isPremium ?? false
},
otherPeerName otherPeerName
).start(next: { [weak self] availableProducts, promoConfiguration, isPremium, otherPeerName in ).start(next: { [weak self] availableProducts, promoConfiguration, isPremium, otherPeerName in
if let strongSelf = self { if let strongSelf = self {
strongSelf.promoConfiguration = promoConfiguration strongSelf.promoConfiguration = promoConfiguration
let hadProducts = strongSelf.products != nil let hadProducts = strongSelf.products != nil
var products: [PremiumProduct] = [] var products: [PremiumProduct] = []
for option in promoConfiguration.premiumProductOptions { for option in promoConfiguration.premiumProductOptions {
if let product = availableProducts.first(where: { $0.id == option.storeProductId }), product.isSubscription { if let product = availableProducts.first(where: { $0.id == option.storeProductId }), product.isSubscription {
@ -2992,10 +3032,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
if !hadProducts { if !hadProducts {
strongSelf.selectedProductId = strongSelf.products?.first?.id strongSelf.selectedProductId = strongSelf.products?.first?.id
if let context = screenContext.context {
for (_, video) in promoConfiguration.videos { 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()) strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: .standalone(resource: video.resource), duration: 3.0).start())
} }
} }
}
strongSelf.updated(transition: .immediate) strongSelf.updated(transition: .immediate)
} }
@ -3007,17 +3049,19 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.emojiPackTitle = info.title self.emojiPackTitle = info.title
self.updated(transition: .immediate) self.updated(transition: .immediate)
} else { } else {
if let context = screenContext.context {
self.emojiFileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]) self.emojiFileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId])
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.emojiFile = result[emojiFileId] self.emojiFile = result[emojiFileId]
strongSelf.updated(transition: .immediate) self.updated(transition: .immediate)
}) })
} }
} }
} }
}
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
@ -3032,12 +3076,17 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
return return
} }
let presentationData = self.screenContext.presentationData
if case let .gift(_, _, _, giftCode) = self.source, let giftCode, giftCode.usedDate == nil { if case let .gift(_, _, _, giftCode) = self.source, let giftCode, giftCode.usedDate == nil {
guard let context = self.screenContext.context else {
return
}
self.inProgress = true self.inProgress = true
self.updateInProgress(true) self.updateInProgress(true)
self.updated(transition: .immediate) 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 |> deliverOnMainQueue).start(error: { [weak self] error in
guard let self else { guard let self else {
return return
@ -3048,8 +3097,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.updated(transition: .immediate) self.updated(transition: .immediate)
if case let .waitForExpiration(date) = error { 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) 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 })) 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 return
} }
guard let inAppPurchaseManager = self.context.inAppPurchaseManager, guard let inAppPurchaseManager = self.screenContext.inAppPurchaseManager,
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }) else { let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }) else {
return return
} }
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil
var hasActiveSubsciption = false 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 { } else if !self.validPurchases.isEmpty && !isUpgrade {
let now = Date() let now = Date()
@ -3089,26 +3135,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
if hasActiveSubsciption { if hasActiveSubsciption {
let errorText = presentationData.strings.Premium_Purchase_OnlyOneSubscriptionAllowed 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) self.present(alertController)
return 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.inProgress = true
self.updateInProgress(true) self.updateInProgress(true)
self.updated(transition: .immediate) self.updated(transition: .immediate)
let purpose: AppStoreTransactionPurpose = isUpgrade ? .upgrade : .subscription let purpose: AppStoreTransactionPurpose = isUpgrade ? .upgrade : .subscription
let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose)
let canPurchasePremium: Signal<Bool, NoError>
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 |> deliverOnMainQueue).start(next: { [weak self] available in
if let strongSelf = self { guard let self else {
return
}
if available { if available {
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, purpose: purpose) self.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, case .purchased = status { if let self, case .purchased = status {
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId) let activation: Signal<Never, AssignAppStoreTransactionError>
if let context = self.screenContext.context {
activation = context.account.postbox.peerView(id: context.account.peerId)
|> castError(AssignAppStoreTransactionError.self) |> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in |> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium { if let peer = view.peers[view.peerId], peer.isPremium {
@ -3121,38 +3181,50 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
return .never() return .never()
} }
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout)) |> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
} else {
activation = .complete()
}
self.activationDisposable.set((activation
|> deliverOnMainQueue).start(error: { [weak self] _ in |> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self { if let self {
strongSelf.inProgress = false self.inProgress = false
strongSelf.updateInProgress(false) self.updateInProgress(false)
strongSelf.updated(transition: .immediate) self.updated(transition: .immediate)
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") if let context = self.screenContext.context {
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
}
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown 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: {})]) let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController) self.present(alertController)
} }
}, completed: { [weak self] in }, completed: { [weak self] in
if let strongSelf = self { guard let self else {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start() return
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.isPremium = true
strongSelf.justBought = true
strongSelf.updated(transition: .easeInOut(duration: 0.25))
strongSelf.completion()
} }
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 }, error: { [weak self] error in
if let strongSelf = self { guard let self else {
strongSelf.inProgress = false return
strongSelf.updateInProgress(false) }
strongSelf.updated(transition: .immediate) self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
var errorText: String? var errorText: String?
switch error { switch error {
@ -3173,18 +3245,18 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
if let errorText = errorText { if let errorText = errorText {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") if let context = self.screenContext.context {
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
} }
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 { } else {
strongSelf.inProgress = false self.inProgress = false
strongSelf.updateInProgress(false) self.updateInProgress(false)
strongSelf.updated(transition: .immediate) self.updated(transition: .immediate)
}
} }
}) })
} }
@ -3201,7 +3273,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
func makeState() -> State { 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 { 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), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
transition: context.transition 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( header = emoji.update(
component: EmojiHeaderComponent( component: EmojiHeaderComponent(
context: context.component.context, context: accountContext,
animationCache: state.animationCache, animationCache: accountContext.animationCache,
animationRenderer: state.animationRenderer, animationRenderer: accountContext.animationRenderer,
placeholderColor: environment.theme.list.mediaPlaceholderColor, placeholderColor: environment.theme.list.mediaPlaceholderColor,
accentColor: environment.theme.list.itemAccentColor, accentColor: environment.theme.list.itemAccentColor,
fileId: fileId, fileId: fileId,
@ -3356,7 +3428,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} else if case .profile = context.component.source { } else if case .profile = context.component.source {
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
} else if case let .gift(fromPeerId, _, duration, _) = context.component.source { } 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 { if duration == 12 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string
} else if duration == 6 { } 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) 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 presentController = context.component.present
let secondaryTitle = secondaryTitle.update( let secondaryTitle = secondaryTitle.update(
component: MultilineTextWithEntitiesComponent( component: MultilineTextWithEntitiesComponent(
context: context.component.context, context: context.component.screenContext.context,
animationCache: context.state.animationCache, animationCache: context.component.screenContext.context?.animationCache,
animationRenderer: context.state.animationRenderer, animationRenderer: context.component.screenContext.context?.animationRenderer,
placeholderColor: environment.theme.list.mediaPlaceholderColor, placeholderColor: environment.theme.list.mediaPlaceholderColor,
text: .plain(secondaryAttributedText), text: .plain(secondaryAttributedText),
horizontalAlignment: .center, horizontalAlignment: .center,
@ -3431,7 +3502,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
} : nil, } : nil,
tapAction: { [weak state, weak environment] _, _ in 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 { for attribute in emojiFile.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
var loadedPack: LoadedStickerPack? var loadedPack: LoadedStickerPack?
@ -3439,7 +3510,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
loadedPack = .result(info: info, items: items, installed: updatedInstalled ?? installed) 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 return false
}, actionPerformed: { added in }, actionPerformed: { added in
updatedInstalled = added updatedInstalled = added
@ -3462,7 +3533,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let scrollContent = scrollContent.update( let scrollContent = scrollContent.update(
component: ScrollComponent<EnvironmentType>( component: ScrollComponent<EnvironmentType>(
content: AnyComponent(PremiumIntroScreenContentComponent( content: AnyComponent(PremiumIntroScreenContentComponent(
context: context.component.context, screenContext: context.component.screenContext,
mode: context.component.mode, mode: context.component.mode,
source: context.component.source, source: context.component.source,
forceDark: context.component.forceDark, forceDark: context.component.forceDark,
@ -3570,8 +3641,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
) )
var isUnusedGift = false var isUnusedGift = false
if case let .gift(fromId, _, _, giftCode) = context.component.source { if case let .gift(fromId, _, _, giftCode) = context.component.source, let accountContext = context.component.screenContext.context {
if let giftCode, giftCode.usedDate == nil, fromId != context.component.context.account.peerId { if let giftCode, giftCode.usedDate == nil, fromId != accountContext.account.peerId {
isUnusedGift = true isUnusedGift = true
} }
} }
@ -3691,12 +3762,70 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
} }
public final class PremiumIntroScreen: ViewControllerComponentContainer { 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<PresentationData, NoError>) {
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 { public enum Mode {
case premium case premium
case business 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 fileprivate let mode: Mode
private var didSetReady = false private var didSetReady = false
@ -3709,18 +3838,24 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
public weak var containerView: UIView? public weak var containerView: UIView?
public var animationColor: UIColor? public var animationColor: UIColor?
public init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) { public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) {
self.context = context 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 self.mode = mode
let presentationData = screenContext.presentationData
var updateInProgressImpl: ((Bool) -> Void)? var updateInProgressImpl: ((Bool) -> Void)?
var pushImpl: ((ViewController) -> Void)? var pushImpl: ((ViewController) -> Void)?
var presentImpl: ((ViewController) -> Void)? var presentImpl: ((ViewController) -> Void)?
var completionImpl: (() -> Void)? var completionImpl: (() -> Void)?
var copyLinkImpl: ((String) -> Void)? var copyLinkImpl: ((String) -> Void)?
var shareLinkImpl: ((String) -> Void)? var shareLinkImpl: ((String) -> Void)?
super.init(context: context, component: PremiumIntroScreenComponent( super.init(component: PremiumIntroScreenComponent(
context: context, screenContext: screenContext,
mode: mode, mode: mode,
source: source, source: source,
forceDark: forceDark, forceDark: forceDark,
@ -3743,9 +3878,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
shareLink: { link in shareLink: { link in
shareLinkImpl?(link) shareLinkImpl?(link)
} }
), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default) ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if modal { if modal {
let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
@ -3756,11 +3889,12 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
} }
updateInProgressImpl = { [weak self] inProgress in updateInProgressImpl = { [weak self] inProgress in
if let strongSelf = self { guard let self else {
strongSelf.navigationItem.leftBarButtonItem?.isEnabled = !inProgress return
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = inProgress
strongSelf.view.disablesInteractiveModalDismiss = inProgress
} }
self.navigationItem.leftBarButtonItem?.isEnabled = !inProgress
self.view.disablesInteractiveTransitionGestureRecognizer = inProgress
self.view.disablesInteractiveModalDismiss = inProgress
} }
presentImpl = { [weak self] c in presentImpl = { [weak self] c in
@ -3789,12 +3923,11 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
} }
self.dismissAllTooltips() 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) 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 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 return
} }
@ -3807,7 +3940,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
HapticFeedback().success() 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)) (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) let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: messages)
@ -3820,7 +3952,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
navigationController.pushViewController(peerSelectionController) navigationController.pushViewController(peerSelectionController)
} }
if case .business = mode { if case .business = mode, case let .accountContext(context) = screenContext {
context.account.viewTracker.keepQuickRepliesApproximatelyUpdated() context.account.viewTracker.keepQuickRepliesApproximatelyUpdated()
context.account.viewTracker.keepBusinessLinksApproximatelyUpdated() context.account.viewTracker.keepBusinessLinksApproximatelyUpdated()
} }

View File

@ -16,6 +16,7 @@ import BundleIconComponent
import Markdown import Markdown
import SolidRoundedButtonNode import SolidRoundedButtonNode
import BlurredBackgroundComponent import BlurredBackgroundComponent
import PremiumCoinComponent
public class PremiumLimitsListScreen: ViewController { public class PremiumLimitsListScreen: ViewController {
final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {

View File

@ -1220,7 +1220,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) } dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) }
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($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[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) }
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }

View File

@ -567,7 +567,7 @@ public extension Api.auth {
public extension Api.auth { public extension Api.auth {
enum SentCode: TypeConstructorDescription { enum SentCode: TypeConstructorDescription {
case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) 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) case sentCodeSuccess(authorization: Api.auth.Authorization)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { 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 << 1) != 0 {nextType!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break break
case .sentCodePaymentRequired(let storeProduct): case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash):
if boxed { if boxed {
buffer.appendInt32(304435204) buffer.appendInt32(-674301568)
} }
serializeString(storeProduct, buffer: buffer, boxed: false) serializeString(storeProduct, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
break break
case .sentCodeSuccess(let authorization): case .sentCodeSuccess(let authorization):
if boxed { if boxed {
@ -601,8 +602,8 @@ public extension Api.auth {
switch self { switch self {
case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): 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)]) 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): case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash):
return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any)]) return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any), ("phoneCodeHash", phoneCodeHash as Any)])
case .sentCodeSuccess(let authorization): case .sentCodeSuccess(let authorization):
return ("sentCodeSuccess", [("authorization", authorization as Any)]) return ("sentCodeSuccess", [("authorization", authorization as Any)])
} }
@ -638,9 +639,12 @@ public extension Api.auth {
public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? { public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? {
var _1: String? var _1: String?
_1 = parseString(reader) _1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil let _c1 = _1 != nil
if _c1 { let _c2 = _2 != nil
return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!) if _c1 && _c2 {
return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!)
} }
else { else {
return nil return nil

View File

@ -73,7 +73,7 @@ public class UnauthorizedAccount {
public let testingEnvironment: Bool public let testingEnvironment: Bool
public let postbox: Postbox public let postbox: Postbox
public let network: Network public let network: Network
private let stateManager: UnauthorizedAccountStateManager let stateManager: UnauthorizedAccountStateManager
private let updateLoginTokenPipe = ValuePipe<Void>() private let updateLoginTokenPipe = ValuePipe<Void>()
public var updateLoginTokenEvents: Signal<Void, NoError> { public var updateLoginTokenEvents: Signal<Void, NoError> {
@ -91,7 +91,7 @@ public class UnauthorizedAccount {
public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>() public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>()
init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) { init(accountManager: AccountManager<TelegramAccountManagerTypes>, networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) {
self.networkArguments = networkArguments self.networkArguments = networkArguments
self.id = id self.id = id
self.rootPath = rootPath self.rootPath = rootPath
@ -101,11 +101,84 @@ public class UnauthorizedAccount {
self.network = network self.network = network
let updateLoginTokenPipe = self.updateLoginTokenPipe let updateLoginTokenPipe = self.updateLoginTokenPipe
let serviceNotificationPipe = self.serviceNotificationPipe let serviceNotificationPipe = self.serviceNotificationPipe
self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: { let masterDatacenterId = Int32(network.mtProto.datacenterId)
var updateSentCodeImpl: ((Api.auth.SentCode) -> Void)?
self.stateManager = UnauthorizedAccountStateManager(
network: network,
updateLoginToken: {
updateLoginTokenPipe.putNext(Void()) updateLoginTokenPipe.putNext(Void())
}, displayServiceNotification: { text in },
updateSentCode: { sentCode in
updateSentCodeImpl?(sentCode)
},
displayServiceNotification: { text in
serviceNotificationPipe.putNext(text) 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() network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in |> map { mode -> Bool in
@ -152,7 +225,7 @@ public class UnauthorizedAccount {
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in |> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> 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) 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 |> 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()) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
return updated return updated
} }
@ -250,7 +323,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
case let unauthorizedState as UnauthorizedAccountState: case let unauthorizedState as UnauthorizedAccountState:
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig) return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in |> 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: case let authorizedState as AuthorizedAccountState:
return postbox.transaction { transaction -> String? in return postbox.transaction { transaction -> String? in
@ -269,7 +342,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig) return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in |> 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))
} }
} }
} }

View File

@ -356,9 +356,8 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
} }
} }
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case let .sentCodePaymentRequired(storeProduct): case .sentCodePaymentRequired:
//TODO:release return .never()
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: "", storeProduct: storeProduct)))
case let .sentCodeSuccess(authorization): case let .sentCodeSuccess(authorization):
switch authorization { switch authorization {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user): case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
@ -519,9 +518,8 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
return .single(.sentCode(account)) return .single(.sentCode(account))
case let .sentCodePaymentRequired(storeProduct): case let .sentCodePaymentRequired(storeProduct, codeHash):
//TODO:release transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
return .single(.sentCode(account)) return .single(.sentCode(account))
case .sentCodeSuccess: case .sentCodeSuccess:
return .single(.loggedIn) return .single(.loggedIn)
@ -628,9 +626,8 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
} }
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case let .sentCodePaymentRequired(storeProduct): case let .sentCodePaymentRequired(storeProduct, codeHash):
//TODO:release transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
case .sentCodeSuccess: case .sentCodeSuccess:
break break
} }
@ -908,9 +905,8 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat
} }
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
case let .sentCodePaymentRequired(storeProduct): case let .sentCodePaymentRequired(storeProduct, codeHash):
//TODO:release transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: phoneCodeHash, storeProduct: storeProduct)))
case .sentCodeSuccess: case .sentCodeSuccess:
break break
} }
@ -974,9 +970,8 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
return .complete() return .complete()
case let .sentCodePaymentRequired(storeProduct): case let .sentCodePaymentRequired(storeProduct, codeHash):
//TODO:release transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: phoneCodeHash, storeProduct: storeProduct)))
return .complete() return .complete()
case .sentCodeSuccess: case .sentCodeSuccess:
return .complete() return .complete()

View File

@ -53,11 +53,18 @@ final class UnauthorizedAccountStateManager {
private var updateService: UnauthorizedUpdateMessageService? private var updateService: UnauthorizedUpdateMessageService?
private let updateServiceDisposable = MetaDisposable() private let updateServiceDisposable = MetaDisposable()
private let updateLoginToken: () -> Void private let updateLoginToken: () -> Void
private let updateSentCode: (Api.auth.SentCode) -> Void
private let displayServiceNotification: (String) -> 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.network = network
self.updateLoginToken = updateLoginToken self.updateLoginToken = updateLoginToken
self.updateSentCode = updateSentCode
self.displayServiceNotification = displayServiceNotification self.displayServiceNotification = displayServiceNotification
} }
@ -65,11 +72,18 @@ final class UnauthorizedAccountStateManager {
self.updateServiceDisposable.dispose() self.updateServiceDisposable.dispose()
} }
func addUpdates(_ updates: Api.Updates) {
self.queue.async {
self.updateService?.addUpdates(updates)
}
}
func reset() { func reset() {
self.queue.async { self.queue.async {
if self.updateService == nil { if self.updateService == nil {
self.updateService = UnauthorizedUpdateMessageService() self.updateService = UnauthorizedUpdateMessageService()
let updateLoginToken = self.updateLoginToken let updateLoginToken = self.updateLoginToken
let updateSentCode = self.updateSentCode
let displayServiceNotification = self.displayServiceNotification let displayServiceNotification = self.displayServiceNotification
self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in
for update in updates { for update in updates {
@ -81,6 +95,8 @@ final class UnauthorizedAccountStateManager {
if popup { if popup {
displayServiceNotification(message) displayServiceNotification(message)
} }
case let .updateSentPhoneCode(sentCode):
updateSentCode(sentCode)
default: default:
break break
} }

View File

@ -182,7 +182,7 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool) case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool)
case awaitingAccountReset(protectedUntil: Int32, number: 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 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) { public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) { 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) self = .awaitingAccountReset(protectedUntil: decoder.decodeInt32ForKey("protectedUntil", orElse: 0), number: decoder.decodeOptionalStringForKey("number"), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
case UnauthorizedAccountStateContentsValue.signUp.rawValue: 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) 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: 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: default:
assertionFailure() assertionFailure()
self = .empty self = .empty
@ -308,11 +307,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
encoder.encodeNil(forKey: "tos") encoder.encodeNil(forKey: "tos")
} }
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") 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.encodeInt32(UnauthorizedAccountStateContentsValue.payment.rawValue, forKey: "v")
encoder.encodeString(number, forKey: "n") encoder.encodeString(number, forKey: "n")
encoder.encodeString(codeHash, forKey: "h") encoder.encodeString(codeHash, forKey: "h")
encoder.encodeString(storeProduct, forKey: "storeProduct") encoder.encodeString(storeProduct, forKey: "storeProduct")
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
} }
} }
@ -384,8 +384,8 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
} else { } else {
return false return false
} }
case let .payment(number, codeHash, storeProduct): case let .payment(number, codeHash, storeProduct, syncContacts):
if case .payment(number, codeHash, storeProduct) = rhs { if case .payment(number, codeHash, storeProduct, syncContacts) = rhs {
return true return true
} else { } else {
return false return false

View File

@ -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<Never, AssignAppStoreTransactionError> { func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose) return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self) |> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in |> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
@ -161,7 +161,26 @@ func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateMana
} }
} }
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in |> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
stateManager?.addUpdates(updates) stateManager.addUpdates(updates)
return .complete()
}
}
}
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: UnauthorizedAccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> 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<Never, AssignAppStoreTransactionError> in
stateManager.addUpdates(updates)
return .complete() return .complete()
} }
} }

View File

@ -164,7 +164,7 @@ public extension TelegramEngineUnauthorized {
} }
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> { public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
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)
} }
} }
} }

View File

@ -1213,9 +1213,9 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
private var currentTheme: PresentationTheme? private var currentTheme: PresentationTheme?
private var currentStrings: PresentationStrings? 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 }) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.stars = stars self.stars = stars
@ -1295,7 +1295,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
} }
) )
if let amount = self.stars { 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 let rawText: String
if self.isPremiumDisabled { if self.isPremiumDisabled {
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
@ -1426,7 +1426,7 @@ private enum ChatEmptyNodeContentType: Equatable {
case greeting case greeting
case topic case topic
case premiumRequired case premiumRequired
case starsRequired case starsRequired(Int64)
} }
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode { private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
@ -1815,8 +1815,8 @@ public final class ChatEmptyNode: ASDisplayNode {
} else if let _ = interfaceState.peerNearbyData { } else if let _ = interfaceState.peerNearbyData {
contentType = .peerNearby contentType = .peerNearby
} else if let peer = peer as? TelegramUser { } else if let peer = peer as? TelegramUser {
if let _ = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil { if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired contentType = .starsRequired(sendPaidMessageStars.value)
} else if interfaceState.isPremiumRequiredForMessaging { } else if interfaceState.isPremiumRequiredForMessaging {
contentType = .premiumRequired contentType = .premiumRequired
} else { } else {
@ -1881,8 +1881,8 @@ public final class ChatEmptyNode: ASDisplayNode {
node = ChatEmptyNodeTopicChatContent(context: self.context) node = ChatEmptyNodeTopicChatContent(context: self.context)
case .premiumRequired: case .premiumRequired:
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil) node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
case .starsRequired: case let .starsRequired(stars):
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: interfaceState.sendPaidMessageStars) node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
} }
self.content = (contentType, node) self.content = (contentType, node)
self.addSubnode(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) 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)) let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))

View File

@ -1044,6 +1044,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var edited = false var edited = false
var viewCount: Int? = nil var viewCount: Int? = nil
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect, messageEffect: messageEffect,
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.message), canViewReactionList: canViewMessageReactionList(message: item.message),

View File

@ -675,6 +675,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message) var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message)
if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview { if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview {
dateReactionsAndPeers = ([], []) dateReactionsAndPeers = ([], [])
@ -688,6 +689,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects), messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring, hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message), canViewReactionList: canViewMessageReactionList(message: message),

View File

@ -130,7 +130,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false needReactions = false
break outer 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))) result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
addedPriceInfo = true addedPriceInfo = true
} }
@ -2276,6 +2276,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: message) 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 }) { if message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) dateReactionsAndPeers = ([], [])
@ -2289,6 +2290,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring, hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message), canViewReactionList: canViewMessageReactionList(message: message),

View File

@ -233,6 +233,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect, messageEffect: messageEffect,
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -195,6 +195,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var areReactionsTags: Bool var areReactionsTags: Bool
var messageEffect: AvailableMessageEffects.MessageEffect? var messageEffect: AvailableMessageEffects.MessageEffect?
var replyCount: Int var replyCount: Int
var starsCount: Int64?
var isPinned: Bool var isPinned: Bool
var hasAutoremove: Bool var hasAutoremove: Bool
var canViewReactionList: Bool var canViewReactionList: Bool
@ -218,6 +219,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
areReactionsTags: Bool, areReactionsTags: Bool,
messageEffect: AvailableMessageEffects.MessageEffect?, messageEffect: AvailableMessageEffects.MessageEffect?,
replyCount: Int, replyCount: Int,
starsCount: Int64?,
isPinned: Bool, isPinned: Bool,
hasAutoremove: Bool, hasAutoremove: Bool,
canViewReactionList: Bool, canViewReactionList: Bool,
@ -240,6 +242,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
self.areReactionsTags = areReactionsTags self.areReactionsTags = areReactionsTags
self.messageEffect = messageEffect self.messageEffect = messageEffect
self.replyCount = replyCount self.replyCount = replyCount
self.starsCount = starsCount
self.isPinned = isPinned self.isPinned = isPinned
self.hasAutoremove = hasAutoremove self.hasAutoremove = hasAutoremove
self.canViewReactionList = canViewReactionList self.canViewReactionList = canViewReactionList
@ -262,6 +265,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
private var repliesIcon: ASImageNode? private var repliesIcon: ASImageNode?
private var selfExpiringIcon: ASImageNode? private var selfExpiringIcon: ASImageNode?
private var replyCountNode: TextNode? private var replyCountNode: TextNode?
private var starsIcon: ASImageNode?
private var starsCountNode: TextNode?
private var type: ChatMessageDateAndStatusType? private var type: ChatMessageDateAndStatusType?
private var theme: ChatPresentationThemeData? private var theme: ChatPresentationThemeData?
@ -316,11 +321,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var currentBackgroundNode = self.backgroundNode var currentBackgroundNode = self.backgroundNode
var currentImpressionIcon = self.impressionIcon var currentImpressionIcon = self.impressionIcon
var currentRepliesIcon = self.repliesIcon var currentRepliesIcon = self.repliesIcon
var currentStarsIcon = self.starsIcon
let currentType = self.type let currentType = self.type
let currentTheme = self.theme let currentTheme = self.theme
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode)
let reactionButtonsContainer = self.reactionButtonsContainer let reactionButtonsContainer = self.reactionButtonsContainer
@ -337,6 +344,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
let clockMinImage: UIImage? let clockMinImage: UIImage?
var impressionImage: UIImage? var impressionImage: UIImage?
var repliesImage: UIImage? var repliesImage: UIImage?
var starsImage: UIImage?
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
@ -404,6 +412,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.incomingDateAndStatusPinnedIcon repliesImage = graphics.incomingDateAndStatusPinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.incomingDateAndStatusRepliesIcon
}
case let .BubbleOutgoing(status): case let .BubbleOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
outgoingStatus = status outgoingStatus = status
@ -420,6 +431,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.outgoingDateAndStatusPinnedIcon repliesImage = graphics.outgoingDateAndStatusPinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.outgoingDateAndStatusRepliesIcon
}
case .ImageIncoming: case .ImageIncoming:
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
backgroundImage = graphics.dateAndStatusMediaBackground backgroundImage = graphics.dateAndStatusMediaBackground
@ -436,6 +450,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon repliesImage = graphics.mediaPinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
}
case let .ImageOutgoing(status): case let .ImageOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
outgoingStatus = status outgoingStatus = status
@ -453,6 +470,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon repliesImage = graphics.mediaPinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
}
case .FreeIncoming: case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText dateColor = serviceColor.primaryText
@ -471,6 +491,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon repliesImage = graphics.freePinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
}
case let .FreeOutgoing(status): case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText dateColor = serviceColor.primaryText
@ -489,6 +512,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned { } else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon repliesImage = graphics.freePinnedIcon
} }
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
}
} }
var updatedDateText = arguments.dateText var updatedDateText = arguments.dateText
@ -541,6 +567,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
currentRepliesIcon = nil 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 { if let outgoingStatus = outgoingStatus {
switch outgoingStatus { switch outgoingStatus {
case .Sending: case .Sending:
@ -652,6 +692,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let reactionSize: CGFloat = 8.0 let reactionSize: CGFloat = 8.0
let reactionSpacing: CGFloat = 2.0 let reactionSpacing: CGFloat = 2.0
@ -676,6 +717,21 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
reactionInset += 12.0 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 { if arguments.messageEffect != nil {
reactionInset += 13.0 reactionInset += 13.0
} }
@ -1237,6 +1293,56 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
replyCountNode.removeFromSupernode() 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()
}
}
} }
}) })
}) })

View File

@ -295,6 +295,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
var rawText = "" var rawText = ""
var rawEntities: [MessageTextEntity] = [] var rawEntities: [MessageTextEntity] = []
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -578,6 +578,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: nil,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -898,6 +898,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: arguments.context.account.peerId, accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage) 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 { if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview {
dateReactionsAndPeers = ([], []) 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 { if let channel = arguments.message.peers[arguments.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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 { if arguments.forcedIsEdited {
@ -956,6 +959,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId), areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId),
messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects), messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode, isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
hasAutoremove: arguments.message.isSelfExpiring, hasAutoremove: arguments.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage), canViewReactionList: canViewMessageReactionList(message: arguments.topMessage),

View File

@ -524,6 +524,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let sentViaBot = false let sentViaBot = false
var viewCount: Int? = nil var viewCount: Int? = nil
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect, messageEffect: messageEffect,
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -83,6 +83,7 @@ public struct ChatMessageDateAndStatus {
public var dateReactions: [MessageReaction] public var dateReactions: [MessageReaction]
public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)] public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)]
public var dateReplies: Int public var dateReplies: Int
public var starsCount: Int64?
public var isPinned: Bool public var isPinned: Bool
public var dateText: String public var dateText: String
@ -93,6 +94,7 @@ public struct ChatMessageDateAndStatus {
dateReactions: [MessageReaction], dateReactions: [MessageReaction],
dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)], dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)],
dateReplies: Int, dateReplies: Int,
starsCount: Int64?,
isPinned: Bool, isPinned: Bool,
dateText: String dateText: String
) { ) {
@ -102,6 +104,7 @@ public struct ChatMessageDateAndStatus {
self.dateReactions = dateReactions self.dateReactions = dateReactions
self.dateReactionPeers = dateReactionPeers self.dateReactionPeers = dateReactionPeers
self.dateReplies = dateReplies self.dateReplies = dateReplies
self.starsCount = starsCount
self.isPinned = isPinned self.isPinned = isPinned
self.dateText = dateText self.dateText = dateText
} }
@ -1118,6 +1121,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId), areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
messageEffect: messageEffect, messageEffect: messageEffect,
replyCount: dateAndStatus.dateReplies, replyCount: dateAndStatus.dateReplies,
starsCount: dateAndStatus.starsCount,
isPinned: dateAndStatus.isPinned, isPinned: dateAndStatus.isPinned,
hasAutoremove: message.isSelfExpiring, hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message), canViewReactionList: canViewMessageReactionList(message: message),

View File

@ -192,6 +192,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -307,6 +307,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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, dateReactions: dateReactionsAndPeers.reactions,
dateReactionPeers: dateReactionsAndPeers.peers, dateReactionPeers: dateReactionsAndPeers.peers,
dateReplies: dateReplies, dateReplies: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText dateText: dateText
) )

View File

@ -1054,6 +1054,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -56,6 +56,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
var viewCount: Int? var viewCount: Int?
var rawText = "" var rawText = ""
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -602,6 +602,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
var edited = false var edited = false
var viewCount: Int? = nil var viewCount: Int? = nil
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.message), canViewReactionList: canViewMessageReactionList(message: item.message),

View File

@ -264,6 +264,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
var viewCount: Int? var viewCount: Int?
var dateReplies = 0 var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage) 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 }) { if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], []) 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 { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count) 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), areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies, replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
hasAutoremove: item.message.isSelfExpiring, hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage), canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -28,7 +28,6 @@ swift_library(
"//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent", "//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/PremiumUI",
"//submodules/Components/BlurredBackgroundComponent", "//submodules/Components/BlurredBackgroundComponent",
"//submodules/Markdown", "//submodules/Markdown",
"//submodules/PresentationDataUtils", "//submodules/PresentationDataUtils",
@ -38,6 +37,7 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/ToastComponent", "//submodules/TelegramUI/Components/ToastComponent",
"//submodules/AvatarNode", "//submodules/AvatarNode",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -19,13 +19,13 @@ import ListItemSliderSelectorComponent
import ListActionItemComponent import ListActionItemComponent
import Markdown import Markdown
import BlurredBackgroundComponent import BlurredBackgroundComponent
import PremiumUI
import PresentationDataUtils import PresentationDataUtils
import PeerListItemComponent import PeerListItemComponent
import TelegramStringFormatting import TelegramStringFormatting
import ContextUI import ContextUI
import BalancedTextComponent import BalancedTextComponent
import AlertComponent import AlertComponent
import PremiumCoinComponent
private func textForTimeout(value: Int32) -> String { private func textForTimeout(value: Int32) -> String {
if value < 3600 { if value < 3600 {

View File

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

View File

@ -48,6 +48,9 @@ public final class PremiumCoinComponent: Component {
public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
public final class Tag { public final class Tag {
public init() {
}
} }
public func matches(tag: Any) -> Bool { public func matches(tag: Any) -> Bool {
@ -58,7 +61,7 @@ public final class PremiumCoinComponent: Component {
} }
private var _ready = Promise<Bool>() private var _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> { public var ready: Signal<Bool, NoError> {
return self._ready.get() return self._ready.get()
} }

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "highprice_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "support_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "verificationcode_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -2366,13 +2366,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
} }
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController { private func mapIntroSource(source: PremiumIntroSource) -> PremiumSource {
var modal = true
let mappedSource: PremiumSource let mappedSource: PremiumSource
switch source { switch source {
case .settings: case .settings:
mappedSource = .settings mappedSource = .settings
modal = false
case .stickers: case .stickers:
mappedSource = .stickers mappedSource = .stickers
case .reactions: case .reactions:
@ -2454,7 +2452,25 @@ public final class SharedAccountContextImpl: SharedAccountContext {
case .paidMessages: case .paidMessages:
mappedSource = .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 controller.wasDismissed = dismissed
return controller return controller
} }