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,38 +170,150 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
return return
} }
self.products = products self.products = products
self.state?.updated() if !self.isUpdating {
self.state?.updated()
}
}) })
} }
self.component = component
if themeUpdated { if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor self.backgroundColor = environment.theme.list.plainBackgroundColor
} }
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let animationHeight: CGFloat = 120.0
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
let bottomPanelPadding: CGFloat = 12.0 let bottomPanelPadding: CGFloat = 12.0
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

File diff suppressed because it is too large Load Diff

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,13 +73,13 @@ 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> {
return self.updateLoginTokenPipe.signal() return self.updateLoginTokenPipe.signal()
} }
private let serviceNotificationPipe = ValuePipe<String>() private let serviceNotificationPipe = ValuePipe<String>()
public var serviceNotificationEvents: Signal<String, NoError> { public var serviceNotificationEvents: Signal<String, NoError> {
return self.serviceNotificationPipe.signal() return self.serviceNotificationPipe.signal()
@ -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)
updateLoginTokenPipe.putNext(Void())
}, displayServiceNotification: { text in var updateSentCodeImpl: ((Api.auth.SentCode) -> Void)?
serviceNotificationPipe.putNext(text) self.stateManager = UnauthorizedAccountStateManager(
}) network: network,
updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
},
updateSentCode: { sentCode in
updateSentCodeImpl?(sentCode)
},
displayServiceNotification: { text in
serviceNotificationPipe.putNext(text)
}
)
updateSentCodeImpl = { [weak self] sentCode in
switch sentCode {
case .sentCodePaymentRequired:
break
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
let _ = postbox.transaction({ transaction in
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, syncContacts) = state.contents {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
}
}).start()
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, _, _, futureAuthToken, user):
let _ = postbox.transaction({ [weak self] transaction in
var syncContacts = true
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, syncContactsValue) = state.contents {
syncContacts = syncContactsValue
}
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
var isSupportUser = false
if let phone = user.phone, phone.hasPrefix("42"), phone.count <= 5 {
isSupportUser = true
}
let state = AuthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { [weak self] transaction -> SendAuthorizationCodeResult in
if let self {
switchToAuthorizedAccount(transaction: transaction, account: self, isSupportUser: isSupportUser)
}
return .loggedIn
}
}).start()
case let .authorizationSignUpRequired(_, termsOfService):
let _ = postbox.transaction({ [weak self] transaction in
if let self {
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, syncContacts) = state.contents {
let _ = beginSignUp(
account: self,
data: AuthorizationSignUpData(
number: number,
codeHash: codeHash,
code: .phoneCode(""),
termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)),
syncContacts: syncContacts
)
).start()
}
}
}).start()
}
}
}
network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get() 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
} }