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 makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController

View File

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

View File

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

View File

@ -14,15 +14,19 @@ import ViewControllerComponent
import MultilineTextComponent
import BalancedTextComponent
import BundleIconComponent
import LottieComponent
import ButtonComponent
import TextFormat
import InAppPurchaseManager
import ConfettiEffect
import PremiumCoinComponent
import Markdown
import CountrySelectionUI
import AccountContext
final class AuthorizationSequencePaymentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let sharedContext: SharedAccountContext
let engine: TelegramEngineUnauthorized
let inAppPurchaseManager: InAppPurchaseManager
let presentationData: PresentationData
@ -31,6 +35,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let storeProduct: String
init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized,
inAppPurchaseManager: InAppPurchaseManager,
presentationData: PresentationData,
@ -38,6 +43,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
phoneCodeHash: String,
storeProduct: String
) {
self.sharedContext = sharedContext
self.engine = engine
self.inAppPurchaseManager = inAppPurchaseManager
self.presentationData = presentationData
@ -93,9 +99,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
self.state?.updated()
let (currency, amount) = storeProduct.priceCurrencyAndAmount
let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount)
let _ = (component.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else {
@ -111,6 +115,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
guard let self, let controller = self.environment?.controller() else {
return
}
self.inProgress = false
self.state?.updated(transition: .immediate)
var errorText: String?
@ -156,7 +161,6 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
self.component = component
self.state = state
if self.component == nil {
@ -166,38 +170,150 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
return
}
self.products = products
self.state?.updated()
if !self.isUpdating {
self.state?.updated()
}
})
}
self.component = component
if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor
}
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let animationHeight: CGFloat = 120.0
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "Coin"),
startingPosition: .begin
component: AnyComponent(PremiumCoinComponent(
mode: .business,
isIntro: true,
isVisible: true,
hasIdleAnimations: true
)),
environment: {},
containerSize: CGSize(width: animationHeight, height: animationHeight)
containerSize: CGSize(width: min(414.0, availableSize.width), height: 184.0)
)
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "SMS Fee", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)))
),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
let textColor = environment.theme.list.itemPrimaryTextColor
let secondaryTextColor = environment.theme.list.itemSecondaryTextColor
let linkColor = environment.theme.list.itemAccentColor
var countryName: String = ""
if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(component.phoneNumber, preferredCountries: [:]) {
countryName = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: environment.strings) ?? country.name
}
var items: [AnyComponentWithIdentity<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 animationView.superview == nil {
self.addSubview(animationView)
}
animationView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) / 2.0), y: 156.0), size: animationSize)
animationView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) / 2.0), y: originY), size: animationSize)
originY += animationSize.height + titleSpacing
}
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: originY), size: titleSize)
originY += titleSize.height + listSpacing
}
if let listView = self.list.view {
if listView.superview == nil {
self.addSubview(listView)
}
listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize)
}
let buttonHeight: CGFloat = 50.0
let bottomPanelPadding: CGFloat = 12.0
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
let sideInset: CGFloat = 16.0
let buttonString = "Sign up for $1"
let priceString: String
if let product = self.products.first(where: { $0.id == component.storeProduct }) {
priceString = product.price
} else {
priceString = ""
}
let buttonString = "Sign up for \(priceString)"
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let buttonSize = self.button.update(
transition: transition,
@ -210,7 +326,12 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
),
content: AnyComponentWithIdentity(
id: AnyHashable(buttonString),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
component: AnyComponent(
VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.regular(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
], spacing: 1.0)
)
),
isEnabled: true,
displaysProgress: self.inProgress,
@ -243,6 +364,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer {
public init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized,
presentationData: PresentationData,
inAppPurchaseManager: InAppPurchaseManager,
@ -252,6 +374,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
back: @escaping () -> Void
) {
super.init(component: AuthorizationSequencePaymentScreenComponent(
sharedContext: sharedContext,
engine: engine,
inAppPurchaseManager: inAppPurchaseManager,
presentationData: presentationData,
@ -260,6 +383,12 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
storeProduct: storeProduct
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)))
loadServerCountryCodes(accountManager: sharedContext.accountManager, engine: engine, completion: { [weak self] in
if let strongSelf = self {
strongSelf.requestLayout(forceUpdate: true, transition: .immediate)
}
})
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@ -285,3 +414,183 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
self.dismiss()
}
}
private final class ParagraphComponent: CombinedComponent {
let title: String
let titleColor: UIColor
let text: String
let textColor: UIColor
let iconName: String
let iconColor: UIColor
let action: (() -> Void)?
public init(
title: String,
titleColor: UIColor,
text: String,
textColor: UIColor,
iconName: String,
iconColor: UIColor,
action: (() -> Void)? = nil
) {
self.title = title
self.titleColor = titleColor
self.text = text
self.textColor = textColor
self.iconName = iconName
self.iconColor = iconColor
self.action = action
}
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.titleColor != rhs.titleColor {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
return true
}
final class State: ComponentState {
var cachedChevronImage: (UIImage, UIColor)?
}
func makeState() -> State {
return State()
}
static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let state = context.state
let leftInset: CGFloat = 64.0
let rightInset: CGFloat = 32.0
let textSideInset: CGFloat = leftInset + 8.0
let spacing: CGFloat = 5.0
let textTopInset: CGFloat = 9.0
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let titleColor = component.titleColor
let textColor = component.textColor
let linkColor = component.iconColor
let titleMarkdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor),
link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== linkColor {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, linkColor)
}
let titleAttributedString = parseMarkdownIntoAttributedString(component.title, attributes: titleMarkdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = titleAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
titleAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: titleAttributedString.string))
}
let title = title.update(
component: MultilineTextComponent(
text: .plain(titleAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
component.action?()
}
}
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textMarkdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
let text = text.update(
component: MultilineTextComponent(
text: .markdown(text: component.text, attributes: textMarkdownAttributes),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
component.action?()
}
}
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
transition: .immediate
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: component.iconColor
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
)
context.add(text
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
)
context.add(icon
.position(CGPoint(x: 47.0, y: textTopInset + 18.0))
)
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 18.0)
}
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ import BundleIconComponent
import Markdown
import SolidRoundedButtonNode
import BlurredBackgroundComponent
import PremiumCoinComponent
public class PremiumLimitsListScreen: ViewController {
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[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) }
dict[304435204] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) }
dict[-674301568] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) }
dict[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) }
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }

View File

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

View File

@ -73,13 +73,13 @@ public class UnauthorizedAccount {
public let testingEnvironment: Bool
public let postbox: Postbox
public let network: Network
private let stateManager: UnauthorizedAccountStateManager
let stateManager: UnauthorizedAccountStateManager
private let updateLoginTokenPipe = ValuePipe<Void>()
public var updateLoginTokenEvents: Signal<Void, NoError> {
return self.updateLoginTokenPipe.signal()
}
private let serviceNotificationPipe = ValuePipe<String>()
public var serviceNotificationEvents: Signal<String, NoError> {
return self.serviceNotificationPipe.signal()
@ -91,7 +91,7 @@ public class UnauthorizedAccount {
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.id = id
self.rootPath = rootPath
@ -101,11 +101,84 @@ public class UnauthorizedAccount {
self.network = network
let updateLoginTokenPipe = self.updateLoginTokenPipe
let serviceNotificationPipe = self.serviceNotificationPipe
self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
}, displayServiceNotification: { text in
serviceNotificationPipe.putNext(text)
})
let masterDatacenterId = Int32(network.mtProto.datacenterId)
var updateSentCodeImpl: ((Api.auth.SentCode) -> Void)?
self.stateManager = UnauthorizedAccountStateManager(
network: network,
updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
},
updateSentCode: { sentCode in
updateSentCodeImpl?(sentCode)
},
displayServiceNotification: { text in
serviceNotificationPipe.putNext(text)
}
)
updateSentCodeImpl = { [weak self] sentCode in
switch sentCode {
case .sentCodePaymentRequired:
break
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
let _ = postbox.transaction({ transaction in
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, syncContacts) = state.contents {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
}
}).start()
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, _, _, futureAuthToken, user):
let _ = postbox.transaction({ [weak self] transaction in
var syncContacts = true
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, syncContactsValue) = state.contents {
syncContacts = syncContactsValue
}
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
var isSupportUser = false
if let phone = user.phone, phone.hasPrefix("42"), phone.count <= 5 {
isSupportUser = true
}
let state = AuthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { [weak self] transaction -> SendAuthorizationCodeResult in
if let self {
switchToAuthorizedAccount(transaction: transaction, account: self, isSupportUser: isSupportUser)
}
return .loggedIn
}
}).start()
case let .authorizationSignUpRequired(_, termsOfService):
let _ = postbox.transaction({ [weak self] transaction in
if let self {
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, syncContacts) = state.contents {
let _ = beginSignUp(
account: self,
data: AuthorizationSignUpData(
number: number,
codeHash: codeHash,
code: .phoneCode(""),
termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)),
syncContacts: syncContacts
)
).start()
}
}
}).start()
}
}
}
network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in
@ -152,7 +225,7 @@ public class UnauthorizedAccount {
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<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)
|> map { network in
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
let updated = UnauthorizedAccount(accountManager: accountManager, networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
return updated
}
@ -250,7 +323,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
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)
|> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
}
case let authorizedState as AuthorizedAccountState:
return postbox.transaction { transaction -> String? in
@ -269,7 +342,7 @@ public func accountWithId(accountManager: AccountManager<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)
|> 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)))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: "", storeProduct: storeProduct)))
case .sentCodePaymentRequired:
return .never()
case let .sentCodeSuccess(authorization):
switch authorization {
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)))
return .single(.sentCode(account))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
return .single(.sentCode(account))
case .sentCodeSuccess:
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)))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
case .sentCodeSuccess:
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)))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: phoneCodeHash, storeProduct: storeProduct)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
case .sentCodeSuccess:
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)))
return .complete()
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: phoneCodeHash, storeProduct: storeProduct)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
return .complete()
case .sentCodeSuccess:
return .complete()

View File

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

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

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)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
@ -161,7 +161,26 @@ func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateMana
}
}
|> 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()
}
}

View File

@ -164,7 +164,7 @@ public extension TelegramEngineUnauthorized {
}
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 currentStrings: PresentationStrings?
private let stars: StarsAmount?
private let stars: Int64?
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: StarsAmount?) {
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: Int64?) {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.stars = stars
@ -1295,7 +1295,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
}
)
if let amount = self.stars {
let starsString = presentationStringsFormattedNumber(Int32(amount.value), interfaceState.dateTimeFormat.groupingSeparator)
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
let rawText: String
if self.isPremiumDisabled {
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
@ -1426,7 +1426,7 @@ private enum ChatEmptyNodeContentType: Equatable {
case greeting
case topic
case premiumRequired
case starsRequired
case starsRequired(Int64)
}
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
@ -1815,8 +1815,8 @@ public final class ChatEmptyNode: ASDisplayNode {
} else if let _ = interfaceState.peerNearbyData {
contentType = .peerNearby
} else if let peer = peer as? TelegramUser {
if let _ = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired(sendPaidMessageStars.value)
} else if interfaceState.isPremiumRequiredForMessaging {
contentType = .premiumRequired
} else {
@ -1881,8 +1881,8 @@ public final class ChatEmptyNode: ASDisplayNode {
node = ChatEmptyNodeTopicChatContent(context: self.context)
case .premiumRequired:
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
case .starsRequired:
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: interfaceState.sendPaidMessageStars)
case let .starsRequired(stars):
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
}
self.content = (contentType, node)
self.addSubnode(node)
@ -1893,7 +1893,12 @@ public final class ChatEmptyNode: ASDisplayNode {
node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
}
}
self.isUserInteractionEnabled = [.peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud].contains(contentType)
switch contentType {
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud:
self.isUserInteractionEnabled = true
default:
self.isUserInteractionEnabled = false
}
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))

View File

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

View File

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

View File

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

View File

@ -233,6 +233,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -246,6 +247,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -300,6 +303,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect,
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -195,6 +195,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var areReactionsTags: Bool
var messageEffect: AvailableMessageEffects.MessageEffect?
var replyCount: Int
var starsCount: Int64?
var isPinned: Bool
var hasAutoremove: Bool
var canViewReactionList: Bool
@ -218,6 +219,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
areReactionsTags: Bool,
messageEffect: AvailableMessageEffects.MessageEffect?,
replyCount: Int,
starsCount: Int64?,
isPinned: Bool,
hasAutoremove: Bool,
canViewReactionList: Bool,
@ -240,6 +242,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
self.areReactionsTags = areReactionsTags
self.messageEffect = messageEffect
self.replyCount = replyCount
self.starsCount = starsCount
self.isPinned = isPinned
self.hasAutoremove = hasAutoremove
self.canViewReactionList = canViewReactionList
@ -262,6 +265,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
private var repliesIcon: ASImageNode?
private var selfExpiringIcon: ASImageNode?
private var replyCountNode: TextNode?
private var starsIcon: ASImageNode?
private var starsCountNode: TextNode?
private var type: ChatMessageDateAndStatusType?
private var theme: ChatPresentationThemeData?
@ -316,11 +321,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var currentBackgroundNode = self.backgroundNode
var currentImpressionIcon = self.impressionIcon
var currentRepliesIcon = self.repliesIcon
var currentStarsIcon = self.starsIcon
let currentType = self.type
let currentTheme = self.theme
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode)
let reactionButtonsContainer = self.reactionButtonsContainer
@ -337,6 +344,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
let clockMinImage: UIImage?
var impressionImage: UIImage?
var repliesImage: UIImage?
var starsImage: UIImage?
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
@ -404,6 +412,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.incomingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.incomingDateAndStatusRepliesIcon
}
case let .BubbleOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
outgoingStatus = status
@ -420,6 +431,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.outgoingDateAndStatusRepliesIcon
}
case .ImageIncoming:
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
backgroundImage = graphics.dateAndStatusMediaBackground
@ -436,6 +450,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
}
case let .ImageOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
outgoingStatus = status
@ -453,6 +470,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
}
case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
@ -471,6 +491,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
}
case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
@ -489,6 +512,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
}
}
var updatedDateText = arguments.dateText
@ -541,6 +567,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
currentRepliesIcon = nil
}
var starsIconSize = CGSize()
if let starsImage = starsImage {
if currentStarsIcon == nil {
let iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displayWithoutProcessing = true
iconNode.displaysAsynchronously = false
currentStarsIcon = iconNode
}
starsIconSize = starsImage.size
} else {
currentStarsIcon = nil
}
if let outgoingStatus = outgoingStatus {
switch outgoingStatus {
case .Sending:
@ -652,6 +692,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
}
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let reactionSize: CGFloat = 8.0
let reactionSpacing: CGFloat = 2.0
@ -676,6 +717,21 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
reactionInset += 12.0
}
if let starsCount = arguments.starsCount, starsCount > 0 {
let countString: String
if starsCount > 1000000 {
countString = "\(starsCount / 1000000)M"
} else if starsCount > 1000 {
countString = "\(starsCount / 1000)K"
} else {
countString = "\(starsCount)"
}
let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
starsCountLayoutAndApply = layoutAndApply
}
if arguments.messageEffect != nil {
reactionInset += 13.0
}
@ -1237,6 +1293,56 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
replyCountNode.removeFromSupernode()
}
}
if let currentStarsIcon = currentStarsIcon {
currentStarsIcon.displaysAsynchronously = false
if currentStarsIcon.image !== starsImage {
currentStarsIcon.image = starsImage
}
if currentStarsIcon.supernode == nil {
strongSelf.starsIcon = currentStarsIcon
strongSelf.addSubnode(currentStarsIcon)
if animation.isAnimated {
currentStarsIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
let starsIconFrame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - starsIconSize.height) / 2.0)), size: starsIconSize)
animation.animator.updateFrame(layer: currentStarsIcon.layer, frame: starsIconFrame, completion: nil)
reactionOffset += 9.0
} else if let starsIcon = strongSelf.starsIcon {
strongSelf.starsIcon = nil
if animation.isAnimated {
starsIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsIcon] _ in
starsIcon?.removeFromSupernode()
})
} else {
starsIcon.removeFromSupernode()
}
}
if let (layout, apply) = starsCountLayoutAndApply {
let node = apply()
if strongSelf.starsCountNode !== node {
strongSelf.starsCountNode?.removeFromSupernode()
strongSelf.addSubnode(node)
strongSelf.starsCountNode = node
if animation.isAnimated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
let starsCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
animation.animator.updateFrame(layer: node.layer, frame: starsCountFrame, completion: nil)
reactionOffset += 4.0 + layout.size.width
} else if let starsCountNode = strongSelf.starsCountNode {
strongSelf.starsCountNode = nil
if animation.isAnimated {
starsCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsCountNode] _ in
starsCountNode?.removeFromSupernode()
})
} else {
starsCountNode.removeFromSupernode()
}
}
}
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -192,6 +192,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -205,6 +206,8 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -284,6 +287,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

@ -307,6 +307,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -323,6 +324,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -373,6 +376,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
dateReactions: dateReactionsAndPeers.reactions,
dateReactionPeers: dateReactionsAndPeers.peers,
dateReplies: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText
)

View File

@ -1054,6 +1054,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -1067,6 +1068,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -1125,6 +1128,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View File

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

View File

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

View File

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

View File

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

View File

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

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 Tag {
public init() {
}
}
public func matches(tag: Any) -> Bool {
@ -58,7 +61,7 @@ public final class PremiumCoinComponent: Component {
}
private var _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> {
public var ready: Signal<Bool, NoError> {
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 {
var modal = true
private func mapIntroSource(source: PremiumIntroSource) -> PremiumSource {
let mappedSource: PremiumSource
switch source {
case .settings:
mappedSource = .settings
modal = false
case .stickers:
mappedSource = .stickers
case .reactions:
@ -2454,7 +2452,25 @@ public final class SharedAccountContextImpl: SharedAccountContext {
case .paidMessages:
mappedSource = .paidMessages
}
let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark)
return mappedSource
}
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController {
var modal = true
if case .settings = source {
modal = false
}
let controller = PremiumIntroScreen(context: context, source: self.mapIntroSource(source: source), modal: modal, forceDark: forceDark)
controller.wasDismissed = dismissed
return controller
}
public func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController {
var modal = true
if case .settings = source {
modal = false
}
let controller = PremiumIntroScreen(screenContext: .sharedContext(sharedContext, engine, inAppPurchaseManager), source: self.mapIntroSource(source: source), modal: modal)
controller.wasDismissed = dismissed
return controller
}