Various improvements

This commit is contained in:
Ilya Laktyushin 2025-03-10 15:32:15 +04:00
parent b864b88935
commit 2ecbaf4507
75 changed files with 1653 additions and 126 deletions

View File

@ -1129,6 +1129,8 @@ public protocol SharedAccountContext: AnyObject {
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
func makeDebugSettingsController(context: AccountContext?) -> ViewController? func makeDebugSettingsController(context: AccountContext?) -> ViewController?
func navigateToCurrentCall() func navigateToCurrentCall()

View File

@ -294,6 +294,44 @@ public struct PremiumConfiguration {
} }
} }
public struct AccountFreezeConfiguration {
public static var defaultValue: AccountFreezeConfiguration {
return AccountFreezeConfiguration(
freezeSinceDate: nil,
freezeUntilDate: nil,
freezeAppealUrl: nil
)
}
public let freezeSinceDate: Int32?
public let freezeUntilDate: Int32?
public let freezeAppealUrl: String?
fileprivate init(
freezeSinceDate: Int32?,
freezeUntilDate: Int32?,
freezeAppealUrl: String?
) {
self.freezeSinceDate = freezeSinceDate
self.freezeUntilDate = freezeUntilDate
self.freezeAppealUrl = freezeAppealUrl
}
public static func with(appConfiguration: AppConfiguration) -> AccountFreezeConfiguration {
let defaultValue = self.defaultValue
if let data = appConfiguration.data {
return AccountFreezeConfiguration(
freezeSinceDate: (data["freeze_since_date"] as? Double).flatMap(Int32.init) ?? defaultValue.freezeSinceDate,
freezeUntilDate: (data["freeze_until_date"] as? Double).flatMap(Int32.init) ?? defaultValue.freezeUntilDate,
freezeAppealUrl: data["freeze_appeal_url"] as? String ?? defaultValue.freezeAppealUrl
)
} else {
return defaultValue
}
}
}
public protocol GiftOptionsScreenProtocol { public protocol GiftOptionsScreenProtocol {
} }

View File

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

View File

@ -20,6 +20,7 @@ import TelegramNotices
import AuthenticationServices import AuthenticationServices
import Markdown import Markdown
import AlertUI import AlertUI
import InAppPurchaseManager
import ObjectiveC import ObjectiveC
private var ObjCKey_Delegate: Int? private var ObjCKey_Delegate: Int?
@ -59,6 +60,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
return TelegramEngineUnauthorized(account: self.account) return TelegramEngineUnauthorized(account: self.account)
} }
private var inAppPurchaseManager: InAppPurchaseManager!
public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), presentationData: PresentationData, openUrl: @escaping (String) -> Void, apiId: Int32, apiHash: String, authorizationCompleted: @escaping () -> Void) { public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), presentationData: PresentationData, openUrl: @escaping (String) -> Void, apiId: Int32, apiHash: String, authorizationCompleted: @escaping () -> Void) {
self.sharedContext = sharedContext self.sharedContext = sharedContext
self.account = account self.account = account
@ -79,6 +82,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
super.init(mode: .single, theme: NavigationControllerTheme(statusBar: navigationStatusBar, navigationBar: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), emptyAreaColor: .black), isFlat: true) super.init(mode: .single, theme: NavigationControllerTheme(statusBar: navigationStatusBar, navigationBar: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), emptyAreaColor: .black), isFlat: true)
self.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(self.engine))
self.stateDisposable = (self.engine.auth.state() self.stateDisposable = (self.engine.auth.state()
|> map { state -> InnerState in |> map { state -> InnerState in
if case .authorized = state { if case .authorized = state {
@ -758,6 +763,18 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
return controller return controller
} }
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
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
}
@available(iOS 13.0, *) @available(iOS 13.0, *)
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
let lastController = self.viewControllers.last as? ViewController let lastController = self.viewControllers.last as? ViewController
@ -1285,6 +1302,13 @@ 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):
var controllers: [ViewController] = []
if !self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController())
}
controllers.append(self.paymentController(number: number, phoneCodeHash: codeHash, storeProduct: storeProduct))
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
} }
} }
} }

View File

@ -0,0 +1,287 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import MultilineTextComponent
import BalancedTextComponent
import BundleIconComponent
import LottieComponent
import ButtonComponent
import TextFormat
import InAppPurchaseManager
import ConfettiEffect
final class AuthorizationSequencePaymentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let engine: TelegramEngineUnauthorized
let inAppPurchaseManager: InAppPurchaseManager
let presentationData: PresentationData
let phoneNumber: String
let phoneCodeHash: String
let storeProduct: String
init(
engine: TelegramEngineUnauthorized,
inAppPurchaseManager: InAppPurchaseManager,
presentationData: PresentationData,
phoneNumber: String,
phoneCodeHash: String,
storeProduct: String
) {
self.engine = engine
self.inAppPurchaseManager = inAppPurchaseManager
self.presentationData = presentationData
self.phoneNumber = phoneNumber
self.phoneCodeHash = phoneCodeHash
self.storeProduct = storeProduct
}
static func ==(lhs: AuthorizationSequencePaymentScreenComponent, rhs: AuthorizationSequencePaymentScreenComponent) -> Bool {
if lhs.storeProduct != rhs.storeProduct {
return false
}
return true
}
final class View: UIView {
private let animation = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let list = ComponentView<Empty>()
private let check = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private var isUpdating: Bool = false
private var component: AuthorizationSequencePaymentScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private var products: [InAppPurchaseManager.Product] = []
private var productsDisposable: Disposable?
private var inProgress = false
override init(frame: CGRect) {
super.init(frame: frame)
self.disablesInteractiveKeyboardGestureRecognizer = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.productsDisposable?.dispose()
}
private func proceed() {
guard let component = self.component, let storeProduct = self.products.first(where: { $0.id == component.storeProduct }) else {
return
}
self.inProgress = true
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 {
return
}
let presentationData = component.presentationData
if available {
let _ = (component.inAppPurchaseManager.buyProduct(storeProduct, quantity: 1, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
let _ = status
let _ = self
}, error: { [weak self] error in
guard let self, let controller = self.environment?.controller() else {
return
}
self.state?.updated(transition: .immediate)
var errorText: String?
switch error {
case .generic:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .network:
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .tryLater:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}
if let errorText {
//addAppLogEvent(postbox: component.engine.account.postbox, type: "premium_gift.promo_screen_fail")
let _ = errorText
let _ = controller
//let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
//controller.present(alertController, in: .window(.root))
}
})
} else {
self.inProgress = false
self.state?.updated(transition: .immediate)
}
})
}
func update(component: AuthorizationSequencePaymentScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
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 {
self.productsDisposable = (component.inAppPurchaseManager.availableProducts
|> deliverOnMainQueue).start(next: { [weak self] products in
guard let self else {
return
}
self.products = products
self.state?.updated()
})
}
if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor
}
let animationHeight: CGFloat = 120.0
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "Coin"),
startingPosition: .begin
)),
environment: {},
containerSize: CGSize(width: animationHeight, height: animationHeight)
)
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)
}
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 buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity(
id: AnyHashable(buttonString),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
),
isEnabled: true,
displaysProgress: self.inProgress,
action: { [weak self] in
self?.proceed()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight)
)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - bottomPanelHeight + bottomPanelPadding), size: buttonSize)
}
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer {
public init(
engine: TelegramEngineUnauthorized,
presentationData: PresentationData,
inAppPurchaseManager: InAppPurchaseManager,
phoneNumber: String,
phoneCodeHash: String,
storeProduct: String,
back: @escaping () -> Void
) {
super.init(component: AuthorizationSequencePaymentScreenComponent(
engine: engine,
inAppPurchaseManager: inAppPurchaseManager,
presentationData: presentationData,
phoneNumber: phoneNumber,
phoneCodeHash: phoneCodeHash,
storeProduct: storeProduct
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.attemptNavigation = { _ in
return false
}
self.navigationBar?.backPressed = {
back()
}
}
public override func loadDisplayNode() {
super.loadDisplayNode()
self.displayNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func cancelPressed() {
self.dismiss()
}
}

View File

@ -1208,6 +1208,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
) )
} }
self.chatListDisplayNode.mainContainerNode.openAccountFreezeInfo = { [weak self] in
guard let self else {
return
}
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
}
self.chatListDisplayNode.mainContainerNode.openPhotoSetup = { [weak self] in self.chatListDisplayNode.mainContainerNode.openPhotoSetup = { [weak self] in
guard let self else { guard let self else {
return return

View File

@ -357,6 +357,9 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
itemNode.listNode.openPhotoSetup = { [weak self] in itemNode.listNode.openPhotoSetup = { [weak self] in
self?.openPhotoSetup?() self?.openPhotoSetup?()
} }
itemNode.listNode.openAccountFreezeInfo = { [weak self] in
self?.openAccountFreezeInfo?()
}
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
let filterId: Int32? let filterId: Int32?
@ -425,6 +428,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
var openStarsTopup: ((Int64?) -> Void)? var openStarsTopup: ((Int64?) -> Void)?
var openWebApp: ((TelegramUser) -> Void)? var openWebApp: ((TelegramUser) -> Void)?
var openPhotoSetup: (() -> Void)? var openPhotoSetup: (() -> Void)?
var openAccountFreezeInfo: (() -> Void)?
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
var didBeginSelectingChats: (() -> Void)? var didBeginSelectingChats: (() -> Void)?
var canExpandHiddenItems: (() -> Bool)? var canExpandHiddenItems: (() -> Bool)?

View File

@ -3261,6 +3261,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { node in }, openAdInfo: { node in
interaction.openAdInfo(node) interaction.openAdInfo(node)
}, openAccountFreezeInfo: {
}) })
chatListInteraction.isSearchMode = true chatListInteraction.isSearchMode = true
@ -5243,6 +5244,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
var isInlineMode = false var isInlineMode = false
if case .topics = key { if case .topics = key {

View File

@ -162,6 +162,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
interaction.isInlineMode = isInlineMode interaction.isInlineMode = isInlineMode

View File

@ -115,6 +115,7 @@ public final class ChatListNodeInteraction {
let openWebApp: (TelegramUser) -> Void let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void let openPhotoSetup: () -> Void
let openAdInfo: (ASDisplayNode) -> Void let openAdInfo: (ASDisplayNode) -> Void
let openAccountFreezeInfo: () -> Void
public var searchTextHighightState: String? public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation? var highlightedChatLocation: ChatListHighlightedLocation?
@ -173,7 +174,8 @@ public final class ChatListNodeInteraction {
editPeer: @escaping (ChatListItem) -> Void, editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void, openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void, openPhotoSetup: @escaping () -> Void,
openAdInfo: @escaping (ASDisplayNode) -> Void openAdInfo: @escaping (ASDisplayNode) -> Void,
openAccountFreezeInfo: @escaping () -> Void
) { ) {
self.activateSearch = activateSearch self.activateSearch = activateSearch
self.peerSelected = peerSelected self.peerSelected = peerSelected
@ -220,6 +222,7 @@ public final class ChatListNodeInteraction {
self.openWebApp = openWebApp self.openWebApp = openWebApp
self.openPhotoSetup = openPhotoSetup self.openPhotoSetup = openPhotoSetup
self.openAdInfo = openAdInfo self.openAdInfo = openAdInfo
self.openAccountFreezeInfo = openAccountFreezeInfo
} }
} }
@ -770,6 +773,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value) nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto: case .setupPhoto:
nodeInteraction?.openPhotoSetup() nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
} }
case .hide: case .hide:
nodeInteraction?.dismissNotice(notice) nodeInteraction?.dismissNotice(notice)
@ -1116,6 +1121,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value) nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto: case .setupPhoto:
nodeInteraction?.openPhotoSetup() nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
} }
case .hide: case .hide:
nodeInteraction?.dismissNotice(notice) nodeInteraction?.dismissNotice(notice)
@ -1239,6 +1246,7 @@ public final class ChatListNode: ListView {
public var openWebApp: ((TelegramUser) -> Void)? public var openWebApp: ((TelegramUser) -> Void)?
public var openPhotoSetup: (() -> Void)? public var openPhotoSetup: (() -> Void)?
public var openAdInfo: ((ASDisplayNode) -> Void)? public var openAdInfo: ((ASDisplayNode) -> Void)?
public var openAccountFreezeInfo: (() -> Void)?
private var theme: PresentationTheme private var theme: PresentationTheme
@ -1899,6 +1907,8 @@ public final class ChatListNode: ListView {
self.openPhotoSetup?() self.openPhotoSetup?()
}, openAdInfo: { [weak self] node in }, openAdInfo: { [weak self] node in
self?.openAdInfo?(node) self?.openAdInfo?(node)
}, openAccountFreezeInfo: { [weak self] in
self?.openAccountFreezeInfo?()
}) })
nodeInteraction.isInlineMode = isInlineMode nodeInteraction.isInlineMode = isInlineMode
@ -1988,6 +1998,16 @@ public final class ChatListNode: ListView {
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init)) let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> AccountFreezeConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
})
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest( let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
context.engine.notices.getServerProvidedSuggestions(), context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(), context.engine.notices.getServerDismissedSuggestions(),
@ -1998,9 +2018,10 @@ public final class ChatListNode: ListView {
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId) TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
), ),
context.account.stateManager.contactBirthdays, context.account.stateManager.contactBirthdays,
starsSubscriptionsContextPromise.get() starsSubscriptionsContextPromise.get(),
accountFreezeConfiguration
) )
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first { if let newSessionReview = newSessionReviews.first {
@ -2036,7 +2057,9 @@ public final class ChatListNode: ListView {
todayBirthdayPeerIds = [] todayBirthdayPeerIds = []
} }
if suggestions.contains(.starsSubscriptionLowBalance) { if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext { if let starsSubscriptionsContext {
return starsSubscriptionsContext.state return starsSubscriptionsContext.state
|> map { state in |> map { state in

View File

@ -92,6 +92,7 @@ public enum ChatListNotice: Equatable {
case premiumGrace case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer]) case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer) case setupPhoto(EnginePeer)
case accountFreeze
} }
enum ChatListNodeEntry: Comparable, Identifiable { enum ChatListNodeEntry: Comparable, Identifiable {

View File

@ -288,6 +288,10 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
avatarPeer = accountPeer avatarPeer = accountPeer
case .accountFreeze:
//TODO:localize
titleString = NSAttributedString(string: "Your account is frozen", font: titleFont, textColor: item.theme.list.itemDestructiveColor)
textString = NSAttributedString(string: "Tap to view details and submit an appeal.", font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
} }
var leftInset: CGFloat = sideInset var leftInset: CGFloat = sideInset

View File

@ -151,8 +151,8 @@ open class ViewControllerComponentContainer: ViewController {
private var currentIsVisible: Bool = false private var currentIsVisible: Bool = false
private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
init(context: AccountContext, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) { init(presentationData: PresentationData, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData
self.controller = controller self.controller = controller
@ -234,7 +234,7 @@ open class ViewControllerComponentContainer: ViewController {
return self.displayNode as! Node return self.displayNode as! Node
} }
private let context: AccountContext private var presentationData: PresentationData
private var theme: Theme private var theme: Theme
public private(set) var component: AnyComponent<ViewControllerComponentContainer.Environment> public private(set) var component: AnyComponent<ViewControllerComponentContainer.Environment>
@ -252,17 +252,19 @@ open class ViewControllerComponentContainer: ViewController {
theme: Theme = .default, theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil
) where C.EnvironmentType == ViewControllerComponentContainer.Environment { ) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component) self.component = AnyComponent(component)
self.theme = theme self.theme = theme
let presentationData: PresentationData var effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
if let updatedPresentationData { if let updatedPresentationData {
presentationData = updatedPresentationData.initial effectiveUpdatedPresentationData = updatedPresentationData
} else { } else {
presentationData = self.context.sharedContext.currentPresentationData.with { $0 } effectiveUpdatedPresentationData = (initial: context.sharedContext.currentPresentationData.with { $0 }, signal: context.sharedContext.presentationData)
} }
let presentationData = effectiveUpdatedPresentationData.initial
self.presentationData = presentationData
let navigationBarPresentationData: NavigationBarPresentationData? let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance { switch navigationBarAppearance {
case .none: case .none:
@ -274,7 +276,47 @@ open class ViewControllerComponentContainer: ViewController {
} }
super.init(navigationBarPresentationData: navigationBarPresentationData) super.init(navigationBarPresentationData: navigationBarPresentationData)
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? self.context.sharedContext.presentationData) self.setupPresentationData(effectiveUpdatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode)
}
public init<C: Component>(
component: C,
navigationBarAppearance: NavigationBarAppearance,
statusBarStyle: StatusBarStyle = .default,
presentationMode: PresentationMode = .default,
theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.component = AnyComponent(component)
self.theme = theme
let presentationData = updatedPresentationData.initial
self.presentationData = presentationData
let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.setupPresentationData(updatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
private func setupPresentationData(_ updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), navigationBarAppearance: NavigationBarAppearance, statusBarStyle: StatusBarStyle, presentationMode: PresentationMode) {
self.presentationDataDisposable = (updatedPresentationData.signal
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
var theme = presentationData.theme var theme = presentationData.theme
@ -329,16 +371,8 @@ open class ViewControllerComponentContainer: ViewController {
} }
} }
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override open func loadDisplayNode() { override open func loadDisplayNode() {
self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme) self.displayNode = Node(presentationData: self.presentationData, controller: self, component: self.component, theme: self.theme)
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }

View File

@ -591,7 +591,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
allSelected = false allSelected = false
} }
var actionTitle: String? var actionTitle: String?
if peerIds.count > 1 { if !"".isEmpty, peerIds.count > 1 {
actionTitle = allSelected ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : strings.Premium_Gift_ContactSelection_SelectAll.uppercased() actionTitle = allSelected ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : strings.Premium_Gift_ContactSelection_SelectAll.uppercased()
} }
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(title.uppercased(), AnyHashable(10 * sectionId + (allSelected ? 1 : 0))), theme: theme, strings: strings, actionTitle: actionTitle, action: { _ in let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(title.uppercased(), AnyHashable(10 * sectionId + (allSelected ? 1 : 0))), theme: theme, strings: strings, actionTitle: actionTitle, action: { _ in

View File

@ -216,7 +216,7 @@ public final class InAppPurchaseManager: NSObject {
case deferred case deferred
} }
private let engine: TelegramEngine private let engine: SomeTelegramEngine
private var products: [Product] = [] private var products: [Product] = []
private var productsPromise = Promise<[Product]>([]) private var productsPromise = Promise<[Product]>([])
@ -231,7 +231,9 @@ public final class InAppPurchaseManager: NSObject {
private let disposableSet = DisposableDict<String>() private let disposableSet = DisposableDict<String>()
public init(engine: TelegramEngine) { private var lastRequestTimestamp: Double?
public init(engine: SomeTelegramEngine) {
self.engine = engine self.engine = engine
super.init() super.init()
@ -255,11 +257,15 @@ public final class InAppPurchaseManager: NSObject {
productRequest.start() productRequest.start()
self.productRequest = productRequest self.productRequest = productRequest
self.lastRequestTimestamp = CFAbsoluteTimeGetCurrent()
} }
public var availableProducts: Signal<[Product], NoError> { public var availableProducts: Signal<[Product], NoError> {
if self.products.isEmpty && self.productRequest == nil { if self.products.isEmpty {
self.requestProducts() if let lastRequestTimestamp, CFAbsoluteTimeGetCurrent() - lastRequestTimestamp > 10.0 {
Logger.shared.log("InAppPurchaseManager", "No available products, rerequest")
self.requestProducts()
}
} }
return self.productsPromise.get() return self.productsPromise.get()
} }
@ -287,7 +293,13 @@ public final class InAppPurchaseManager: NSObject {
return .fail(.cantMakePayments) return .fail(.cantMakePayments)
} }
let accountPeerId = "\(self.engine.account.peerId.toInt64())" let accountPeerId: String
switch self.engine {
case let .authorized(engine):
accountPeerId = "\(engine.account.peerId.toInt64())"
case let .unauthorized(engine):
accountPeerId = "\(engine.account.id.int64)"
}
Logger.shared.log("InAppPurchaseManager", "Buying: account \(accountPeerId), product \(product.skProduct.productIdentifier), price \(product.price)") Logger.shared.log("InAppPurchaseManager", "Buying: account \(accountPeerId), product \(product.skProduct.productIdentifier), price \(product.price)")
@ -399,7 +411,13 @@ private func getReceiptData() -> Data? {
extension InAppPurchaseManager: SKPaymentTransactionObserver { extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
self.stateQueue.async { self.stateQueue.async {
let accountPeerId = "\(self.engine.account.peerId.toInt64())" let accountPeerId: String
switch self.engine {
case let .authorized(engine):
accountPeerId = "\(engine.account.peerId.toInt64())"
case let .unauthorized(engine):
accountPeerId = "\(engine.account.id.int64)"
}
let paymentContexts = self.paymentContexts let paymentContexts = self.paymentContexts
@ -519,7 +537,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
(purpose (purpose
|> castError(AssignAppStoreTransactionError.self) |> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in |> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose) switch self.engine {
case let .authorized(engine):
return engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose)
case let .unauthorized(engine):
return engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose)
}
}).start(error: { [weak self] _ in }).start(error: { [weak self] _ in
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transactions [\(transactionIds)] failed to assign") Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transactions [\(transactionIds)] failed to assign")
for transaction in transactions { for transaction in transactions {
@ -551,8 +574,15 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
self.onRestoreCompletion = nil self.onRestoreCompletion = nil
if let receiptData = getReceiptData() { if let receiptData = getReceiptData() {
let signal: Signal<Never, AssignAppStoreTransactionError>
switch self.engine {
case let .authorized(engine):
signal = engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore)
case let .unauthorized(engine):
signal = engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore)
}
self.disposableSet.set( self.disposableSet.set(
self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore).start(error: { error in signal.start(error: { error in
Queue.mainQueue().async { Queue.mainQueue().async {
if case .serverProvided = error { if case .serverProvided = error {
onRestoreCompletion(.succeed(true)) onRestoreCompletion(.succeed(true))
@ -586,14 +616,17 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
} }
private func debugSaveReceipt(receiptData: Data) { private func debugSaveReceipt(receiptData: Data) {
guard case let .authorized(engine) = self.engine else {
return
}
let id = Int64.random(in: Int64.min ... Int64.max) let id = Int64.random(in: Int64.min ... Int64.max)
let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false)
self.engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData) engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData)
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")], alternativeRepresentations: []) let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")], alternativeRepresentations: [])
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let _ = enqueueMessages(account: self.engine.account, peerId: self.engine.account.peerId, messages: [message]).start() let _ = enqueueMessages(account: engine.account, peerId: engine.account.peerId, messages: [message]).start()
} }
} }
@ -625,6 +658,9 @@ private final class PendingInAppPurchaseState: Codable {
case users case users
case text case text
case entities case entities
case restore
case phoneNumber
case phoneCodeHash
} }
enum PurposeType: Int32 { enum PurposeType: Int32 {
@ -637,6 +673,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars case stars
case starsGift case starsGift
case starsGiveaway case starsGiveaway
case authCode
} }
case subscription case subscription
@ -648,6 +685,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars(count: Int64) case stars(count: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64) case starsGift(peerId: EnginePeer.Id, count: Int64)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, users: Int32) case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, users: Int32)
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String)
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
@ -704,6 +742,12 @@ private final class PendingInAppPurchaseState: Codable {
untilDate: try container.decode(Int32.self, forKey: .untilDate), untilDate: try container.decode(Int32.self, forKey: .untilDate),
users: try container.decode(Int32.self, forKey: .users) users: try container.decode(Int32.self, forKey: .users)
) )
case .authCode:
self = .authCode(
restore: try container.decode(Bool.self, forKey: .restore),
phoneNumber: try container.decode(String.self, forKey: .phoneNumber),
phoneCodeHash: try container.decode(String.self, forKey: .phoneCodeHash)
)
default: default:
throw DecodingError.generic throw DecodingError.generic
} }
@ -757,6 +801,11 @@ private final class PendingInAppPurchaseState: Codable {
try container.encode(randomId, forKey: .randomId) try container.encode(randomId, forKey: .randomId)
try container.encode(untilDate, forKey: .untilDate) try container.encode(untilDate, forKey: .untilDate)
try container.encode(users, forKey: .users) try container.encode(users, forKey: .users)
case let .authCode(restore, phoneNumber, phoneCodeHash):
try container.encode(PurposeType.authCode.rawValue, forKey: .type)
try container.encode(restore, forKey: .restore)
try container.encode(phoneNumber, forKey: .phoneNumber)
try container.encode(phoneCodeHash, forKey: .phoneCodeHash)
} }
} }
@ -780,6 +829,8 @@ private final class PendingInAppPurchaseState: Codable {
self = .starsGift(peerId: peerId, count: count) self = .starsGift(peerId: peerId, count: count)
case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _, users): case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _, users):
self = .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, users: users) self = .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, users: users)
case let .authCode(restore, phoneNumber, phoneCodeHash, _, _):
self = .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash)
} }
} }
@ -804,6 +855,8 @@ private final class PendingInAppPurchaseState: Codable {
return .starsGift(peerId: peerId, count: count, currency: currency, amount: amount) return .starsGift(peerId: peerId, count: count, currency: currency, amount: amount)
case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, users): case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, users):
return .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users) return .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users)
case let .authCode(restore, phoneNumber, phoneCodeHash):
return .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount)
} }
} }
} }
@ -831,23 +884,41 @@ private final class PendingInAppPurchaseState: Codable {
} }
} }
private func pendingInAppPurchaseState(engine: TelegramEngine, productId: String) -> Signal<PendingInAppPurchaseState?, NoError> { private func pendingInAppPurchaseState(engine: SomeTelegramEngine, productId: String) -> Signal<PendingInAppPurchaseState?, NoError> {
let key = EngineDataBuffer(length: 8) let key = EngineDataBuffer(length: 8)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue)) key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)) switch engine {
|> map { entry -> PendingInAppPurchaseState? in case let .authorized(engine):
return entry?.get(PendingInAppPurchaseState.self) return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key))
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
}
case let .unauthorized(engine):
return engine.itemCache.get(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
}
} }
} }
private func updatePendingInAppPurchaseState(engine: TelegramEngine, productId: String, content: PendingInAppPurchaseState?) -> Signal<Never, NoError> { private func updatePendingInAppPurchaseState(engine: SomeTelegramEngine, productId: String, content: PendingInAppPurchaseState?) -> Signal<Never, NoError> {
let key = EngineDataBuffer(length: 8) let key = EngineDataBuffer(length: 8)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue)) key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content) switch engine {
} else { case let .authorized(engine):
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key) if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
}
case let .unauthorized(engine):
if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
}
} }
} }

View File

@ -898,7 +898,7 @@
} }
restartRequest = true; restartRequest = true;
} }
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) { else if ((rpcError.errorCode == 420 && [rpcError.errorDescription rangeOfString:@"FROZEN_METHOD_INVALID"].location == NSNotFound) || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
if (request.errorContext == nil) if (request.errorContext == nil)
request.errorContext = [[MTRequestErrorContext alloc] init]; request.errorContext = [[MTRequestErrorContext alloc] init];

View File

@ -233,6 +233,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -382,6 +382,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
func makeChatListItem( func makeChatListItem(

View File

@ -84,7 +84,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1778593322] = { return Api.BotApp.parse_botApp($0) } dict[-1778593322] = { return Api.BotApp.parse_botApp($0) }
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) } dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) }
dict[-912582320] = { return Api.BotAppSettings.parse_botAppSettings($0) } dict[-912582320] = { return Api.BotAppSettings.parse_botAppSettings($0) }
dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) } dict[-1892371723] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) }
dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) }
dict[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($0) } dict[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($0) }
dict[1877059713] = { return Api.BotCommandScope.parse_botCommandScopeChats($0) } dict[1877059713] = { return Api.BotCommandScope.parse_botCommandScopeChats($0) }
@ -118,6 +118,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) } dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) }
dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) } dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) }
dict[-1198722189] = { return Api.BusinessBotRecipients.parse_businessBotRecipients($0) } dict[-1198722189] = { return Api.BusinessBotRecipients.parse_businessBotRecipients($0) }
dict[-1604170505] = { return Api.BusinessBotRights.parse_businessBotRights($0) }
dict[-1263638929] = { return Api.BusinessChatLink.parse_businessChatLink($0) } dict[-1263638929] = { return Api.BusinessChatLink.parse_businessChatLink($0) }
dict[-451302485] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($0) } dict[-451302485] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($0) }
dict[1510606445] = { return Api.BusinessIntro.parse_businessIntro($0) } dict[1510606445] = { return Api.BusinessIntro.parse_businessIntro($0) }
@ -224,7 +225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1713193015] = { return Api.ChatReactions.parse_chatReactionsSome($0) } dict[1713193015] = { return Api.ChatReactions.parse_chatReactionsSome($0) }
dict[-1390068360] = { return Api.CodeSettings.parse_codeSettings($0) } dict[-1390068360] = { return Api.CodeSettings.parse_codeSettings($0) }
dict[-870702050] = { return Api.Config.parse_config($0) } dict[-870702050] = { return Api.Config.parse_config($0) }
dict[-1123645951] = { return Api.ConnectedBot.parse_connectedBot($0) } dict[-849058964] = { return Api.ConnectedBot.parse_connectedBot($0) }
dict[429997937] = { return Api.ConnectedBotStarRef.parse_connectedBotStarRef($0) } dict[429997937] = { return Api.ConnectedBotStarRef.parse_connectedBotStarRef($0) }
dict[341499403] = { return Api.Contact.parse_contact($0) } dict[341499403] = { return Api.Contact.parse_contact($0) }
dict[496600883] = { return Api.ContactBirthday.parse_contactBirthday($0) } dict[496600883] = { return Api.ContactBirthday.parse_contactBirthday($0) }
@ -480,6 +481,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[853188252] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) } dict[853188252] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) } dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) } dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[-1682807955] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentAuthCode($0) }
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) } dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
dict[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) } dict[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) }
dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) } dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
@ -1110,6 +1112,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) } dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) }
dict[969307186] = { return Api.Update.parse_updateSavedReactionTags($0) } dict[969307186] = { return Api.Update.parse_updateSavedReactionTags($0) }
dict[1960361625] = { return Api.Update.parse_updateSavedRingtones($0) } dict[1960361625] = { return Api.Update.parse_updateSavedRingtones($0) }
dict[1347068303] = { return Api.Update.parse_updateSentPhoneCode($0) }
dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) } dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) }
dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) } dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) }
dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) } dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) }
@ -1217,6 +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[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) }
@ -1589,6 +1593,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRecipients: case let _1 as Api.BusinessBotRecipients:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRights:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessChatLink: case let _1 as Api.BusinessChatLink:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.BusinessGreetingMessage: case let _1 as Api.BusinessGreetingMessage:

View File

@ -1170,27 +1170,28 @@ public extension Api {
} }
public extension Api { public extension Api {
enum BotBusinessConnection: TypeConstructorDescription { enum BotBusinessConnection: TypeConstructorDescription {
case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32) case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32, rights: Api.BusinessBotRights?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date): case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights):
if boxed { if boxed {
buffer.appendInt32(-1989921868) buffer.appendInt32(-1892371723)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(connectionId, buffer: buffer, boxed: false) serializeString(connectionId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt32(dcId, buffer: buffer, boxed: false) serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {rights!.serialize(buffer, true)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date): case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights):
return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any)]) return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any), ("rights", rights as Any)])
} }
} }
@ -1205,13 +1206,18 @@ public extension Api {
_4 = reader.readInt32() _4 = reader.readInt32()
var _5: Int32? var _5: Int32?
_5 = reader.readInt32() _5 = reader.readInt32()
var _6: Api.BusinessBotRights?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 { let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6)
} }
else { else {
return nil return nil

View File

@ -116,6 +116,7 @@ public extension Api {
} }
public extension Api { public extension Api {
indirect enum InputStorePaymentPurpose: TypeConstructorDescription { indirect enum InputStorePaymentPurpose: TypeConstructorDescription {
case inputStorePaymentAuthCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64)
case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64) case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64)
case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64, message: Api.TextWithEntities?) case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64, message: Api.TextWithEntities?)
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
@ -126,6 +127,16 @@ public extension Api {
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .inputStorePaymentAuthCode(let flags, let phoneNumber, let phoneCodeHash, let currency, let amount):
if boxed {
buffer.appendInt32(-1682807955)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
case .inputStorePaymentGiftPremium(let userId, let currency, let amount): case .inputStorePaymentGiftPremium(let userId, let currency, let amount):
if boxed { if boxed {
buffer.appendInt32(1634697192) buffer.appendInt32(1634697192)
@ -223,6 +234,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .inputStorePaymentAuthCode(let flags, let phoneNumber, let phoneCodeHash, let currency, let amount):
return ("inputStorePaymentAuthCode", [("flags", flags as Any), ("phoneNumber", phoneNumber as Any), ("phoneCodeHash", phoneCodeHash as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentGiftPremium(let userId, let currency, let amount): case .inputStorePaymentGiftPremium(let userId, let currency, let amount):
return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)]) return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount, let message): case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount, let message):
@ -240,6 +253,29 @@ public extension Api {
} }
} }
public static func parse_inputStorePaymentAuthCode(_ reader: BufferReader) -> InputStorePaymentPurpose? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: String?
_4 = parseString(reader)
var _5: Int64?
_5 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, currency: _4!, amount: _5!)
}
else {
return nil
}
}
public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? { public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? {
var _1: Api.InputUser? var _1: Api.InputUser?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -1103,6 +1103,7 @@ public extension Api {
case updateSavedGifs case updateSavedGifs
case updateSavedReactionTags case updateSavedReactionTags
case updateSavedRingtones case updateSavedRingtones
case updateSentPhoneCode(sentCode: Api.auth.SentCode)
case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction)
case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity])
case updateSmsJob(jobId: String) case updateSmsJob(jobId: String)
@ -2198,6 +2199,12 @@ public extension Api {
buffer.appendInt32(1960361625) buffer.appendInt32(1960361625)
} }
break
case .updateSentPhoneCode(let sentCode):
if boxed {
buffer.appendInt32(1347068303)
}
sentCode.serialize(buffer, true)
break break
case .updateSentStoryReaction(let peer, let storyId, let reaction): case .updateSentStoryReaction(let peer, let storyId, let reaction):
if boxed { if boxed {
@ -2602,6 +2609,8 @@ public extension Api {
return ("updateSavedReactionTags", []) return ("updateSavedReactionTags", [])
case .updateSavedRingtones: case .updateSavedRingtones:
return ("updateSavedRingtones", []) return ("updateSavedRingtones", [])
case .updateSentPhoneCode(let sentCode):
return ("updateSentPhoneCode", [("sentCode", sentCode as Any)])
case .updateSentStoryReaction(let peer, let storyId, let reaction): case .updateSentStoryReaction(let peer, let storyId, let reaction):
return ("updateSentStoryReaction", [("peer", peer as Any), ("storyId", storyId as Any), ("reaction", reaction as Any)]) return ("updateSentStoryReaction", [("peer", peer as Any), ("storyId", storyId as Any), ("reaction", reaction as Any)])
case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities): case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities):
@ -4803,6 +4812,19 @@ public extension Api {
public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? { public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? {
return Api.Update.updateSavedRingtones return Api.Update.updateSavedRingtones
} }
public static func parse_updateSentPhoneCode(_ reader: BufferReader) -> Update? {
var _1: Api.auth.SentCode?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
let _c1 = _1 != nil
if _c1 {
return Api.Update.updateSentPhoneCode(sentCode: _1!)
}
else {
return nil
}
}
public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? {
var _1: Api.Peer? var _1: Api.Peer?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -567,6 +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 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) {
@ -581,6 +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):
if boxed {
buffer.appendInt32(304435204)
}
serializeString(storeProduct, buffer: buffer, boxed: false)
break
case .sentCodeSuccess(let authorization): case .sentCodeSuccess(let authorization):
if boxed { if boxed {
buffer.appendInt32(596704836) buffer.appendInt32(596704836)
@ -594,6 +601,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):
return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any)])
case .sentCodeSuccess(let authorization): case .sentCodeSuccess(let authorization):
return ("sentCodeSuccess", [("authorization", authorization as Any)]) return ("sentCodeSuccess", [("authorization", authorization as Any)])
} }
@ -626,6 +635,17 @@ public extension Api.auth {
return nil return nil
} }
} }
public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!)
}
else {
return nil
}
}
public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? {
var _1: Api.auth.Authorization? var _1: Api.auth.Authorization?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -54,6 +54,42 @@ public extension Api {
} }
} }
public extension Api {
enum BusinessBotRights: TypeConstructorDescription {
case businessBotRights(flags: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .businessBotRights(let flags):
if boxed {
buffer.appendInt32(-1604170505)
}
serializeInt32(flags, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .businessBotRights(let flags):
return ("businessBotRights", [("flags", flags as Any)])
}
}
public static func parse_businessBotRights(_ reader: BufferReader) -> BusinessBotRights? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.BusinessBotRights.businessBotRights(flags: _1!)
}
else {
return nil
}
}
}
}
public extension Api { public extension Api {
enum BusinessChatLink: TypeConstructorDescription { enum BusinessChatLink: TypeConstructorDescription {
case businessChatLink(flags: Int32, link: String, message: String, entities: [Api.MessageEntity]?, title: String?, views: Int32) case businessChatLink(flags: Int32, link: String, message: String, entities: [Api.MessageEntity]?, title: String?, views: Int32)

View File

@ -1604,13 +1604,14 @@ public extension Api.functions.account {
} }
} }
public extension Api.functions.account { public extension Api.functions.account {
static func updateConnectedBot(flags: Int32, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func updateConnectedBot(flags: Int32, rights: Api.BusinessBotRights?, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1138250269) buffer.appendInt32(1721797758)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {rights!.serialize(buffer, true)}
bot.serialize(buffer, true) bot.serialize(buffer, true)
recipients.serialize(buffer, true) recipients.serialize(buffer, true)
return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("rights", String(describing: rights)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -9063,11 +9064,11 @@ public extension Api.functions.payments {
} }
} }
public extension Api.functions.payments { public extension Api.functions.payments {
static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func canPurchaseStore(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1614700874) buffer.appendInt32(1339842215)
purpose.serialize(buffer, true) purpose.serialize(buffer, true)
return (FunctionDescription(name: "payments.canPurchasePremium", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in return (FunctionDescription(name: "payments.canPurchaseStore", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Bool? var result: Api.Bool?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -664,25 +664,26 @@ public extension Api {
} }
public extension Api { public extension Api {
enum ConnectedBot: TypeConstructorDescription { enum ConnectedBot: TypeConstructorDescription {
case connectedBot(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients) case connectedBot(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients, rights: Api.BusinessBotRights)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .connectedBot(let flags, let botId, let recipients): case .connectedBot(let flags, let botId, let recipients, let rights):
if boxed { if boxed {
buffer.appendInt32(-1123645951) buffer.appendInt32(-849058964)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false) serializeInt64(botId, buffer: buffer, boxed: false)
recipients.serialize(buffer, true) recipients.serialize(buffer, true)
rights.serialize(buffer, true)
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .connectedBot(let flags, let botId, let recipients): case .connectedBot(let flags, let botId, let recipients, let rights):
return ("connectedBot", [("flags", flags as Any), ("botId", botId as Any), ("recipients", recipients as Any)]) return ("connectedBot", [("flags", flags as Any), ("botId", botId as Any), ("recipients", recipients as Any), ("rights", rights as Any)])
} }
} }
@ -695,11 +696,16 @@ public extension Api {
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.BusinessBotRecipients _3 = Api.parse(reader, signature: signature) as? Api.BusinessBotRecipients
} }
var _4: Api.BusinessBotRights?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights
}
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
if _c1 && _c2 && _c3 { let _c4 = _4 != nil
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!) if _c1 && _c2 && _c3 && _c4 {
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!)
} }
else { else {
return nil return nil

View File

@ -356,6 +356,9 @@ 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):
//TODO:release
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):
@ -515,6 +518,10 @@ 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))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
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)
@ -621,6 +628,9 @@ 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):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
case .sentCodeSuccess: case .sentCodeSuccess:
break break
} }
@ -898,6 +908,9 @@ 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):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: phoneCodeHash, storeProduct: storeProduct)))
case .sentCodeSuccess: case .sentCodeSuccess:
break break
} }
@ -960,6 +973,10 @@ 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()
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
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

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 200 return 201
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -139,6 +139,7 @@ private enum UnauthorizedAccountStateContentsValue: Int32 {
case signUp = 5 case signUp = 5
case passwordRecovery = 6 case passwordRecovery = 6
case awaitingAccountReset = 7 case awaitingAccountReset = 7
case payment = 8
} }
public struct UnauthorizedAccountTermsOfService: PostboxCoding, Equatable { public struct UnauthorizedAccountTermsOfService: PostboxCoding, Equatable {
@ -181,6 +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)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) { switch decoder.decodeInt32ForKey("v", orElse: 0) {
@ -214,6 +216,9 @@ 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:
self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""))
default: default:
assertionFailure() assertionFailure()
self = .empty self = .empty
@ -303,6 +308,11 @@ 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):
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.payment.rawValue, forKey: "v")
encoder.encodeString(number, forKey: "n")
encoder.encodeString(codeHash, forKey: "h")
encoder.encodeString(storeProduct, forKey: "storeProduct")
} }
} }
@ -374,6 +384,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
} else { } else {
return false return false
} }
case let .payment(number, codeHash, storeProduct):
if case .payment(number, codeHash, storeProduct) = rhs {
return true
} else {
return false
}
} }
} }
} }

View File

@ -113,7 +113,7 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, api
} else { } else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
} }
case .sentCodeSuccess: case .sentCodeSuccess, .sentCodePaymentRequired:
return .never() return .never()
} }
} }
@ -188,7 +188,7 @@ private func internalResendChangeAccountPhoneNumberVerification(account: Account
} else { } else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
} }
case .sentCodeSuccess: case .sentCodeSuccess, .sentCodePaymentRequired:
return .never() return .never()
} }
} }

View File

@ -34,7 +34,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) ->
parsedNextType = AuthorizationCodeNextType(apiType: nextType) parsedNextType = AuthorizationCodeNextType(apiType: nextType)
} }
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)) return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess: case .sentCodeSuccess, .sentCodePaymentRequired:
return .never() return .never()
} }
} }
@ -57,7 +57,7 @@ func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber
parsedNextType = AuthorizationCodeNextType(apiType: nextType) parsedNextType = AuthorizationCodeNextType(apiType: nextType)
} }
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)) return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess: case .sentCodeSuccess, .sentCodePaymentRequired:
return .never() return .never()
} }
} }

View File

@ -38,3 +38,44 @@ public extension TelegramEngine {
} }
} }
} }
public extension TelegramEngineUnauthorized {
final class ItemCache {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func put<T: Codable>(collectionId: Int8, id: EngineDataBuffer, item: T) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
if let entry = CodableEntry(item) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id), entry: entry)
}
}
|> ignoreValues
}
public func get(collectionId: Int8, id: EngineDataBuffer) -> Signal<CodableEntry?, NoError> {
return self.account.postbox.transaction { transaction -> CodableEntry? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id))
}
}
public func remove(collectionId: Int8, id: EngineDataBuffer) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
transaction.removeItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id))
}
|> ignoreValues
}
public func clear(collectionIds: [Int8]) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
for id in collectionIds {
transaction.clearItemCacheCollection(collectionId: id)
}
}
|> ignoreValues
}
}
}

View File

@ -1072,7 +1072,7 @@ public func _internal_setAccountConnectedBot(account: Account, bot: TelegramAcco
flags |= 1 << 1 flags |= 1 << 1
} }
return account.network.request(Api.functions.account.updateConnectedBot(flags: flags, bot: mappedBot, recipients: mappedRecipients)) return account.network.request(Api.functions.account.updateConnectedBot(flags: flags, rights: nil, bot: mappedBot, recipients: mappedRecipients))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View File

@ -20,9 +20,10 @@ public enum AppStoreTransactionPurpose {
case stars(count: Int64, currency: String, amount: Int64) case stars(count: Int64, currency: String, amount: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64) case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32) case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64)
} }
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> { private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
switch purpose { switch purpose {
case .subscription, .upgrade, .restore: case .subscription, .upgrade, .restore:
var flags: Int32 = 0 var flags: Int32 = 0
@ -36,7 +37,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
} }
return .single(.inputStorePaymentPremiumSubscription(flags: flags)) return .single(.inputStorePaymentPremiumSubscription(flags: flags))
case let .gift(peerId, currency, amount): case let .gift(peerId, currency, amount):
return account.postbox.loadedPeerWithId(peerId) return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in |> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else { guard let inputUser = apiInputUser(peer) else {
return .complete() return .complete()
@ -44,7 +45,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount)) return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
} }
case let .giftCode(peerIds, boostPeerId, currency, amount, text, entities): case let .giftCode(peerIds, boostPeerId, currency, amount, text, entities):
return account.postbox.transaction { transaction -> Api.InputStorePaymentPurpose in return postbox.transaction { transaction -> Api.InputStorePaymentPurpose in
var flags: Int32 = 0 var flags: Int32 = 0
var apiBoostPeer: Api.InputPeer? var apiBoostPeer: Api.InputPeer?
var apiInputUsers: [Api.InputUser] = [] var apiInputUsers: [Api.InputUser] = []
@ -69,7 +70,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount, message: message) return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount, message: message)
} }
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount): case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else { guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete() return .complete()
} }
@ -101,7 +102,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
case let .stars(count, currency, amount): case let .stars(count, currency, amount):
return .single(.inputStorePaymentStarsTopup(stars: count, currency: currency, amount: amount)) return .single(.inputStorePaymentStarsTopup(stars: count, currency: currency, amount: amount))
case let .starsGift(peerId, count, currency, amount): case let .starsGift(peerId, count, currency, amount):
return account.postbox.loadedPeerWithId(peerId) return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in |> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else { guard let inputUser = apiInputUser(peer) else {
return .complete() return .complete()
@ -109,7 +110,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentStarsGift(userId: inputUser, stars: count, currency: currency, amount: amount)) return .single(.inputStorePaymentStarsGift(userId: inputUser, stars: count, currency: currency, amount: amount))
} }
case let .starsGiveaway(stars, boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, users): case let .starsGiveaway(stars, boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, users):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else { guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete() return .complete()
} }
@ -138,14 +139,20 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users)) return .single(.inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users))
} }
|> switchToLatest |> switchToLatest
case let .authCode(restore, phoneNumber, phoneCodeHash, currency, amount):
var flags: Int32 = 0
if restore {
flags |= (1 << 0)
}
return .single(.inputStorePaymentAuthCode(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount))
} }
} }
func _internal_sendAppStoreReceipt(account: Account, 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(account: account, 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
return account.network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose)) return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in |> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 { if error.errorCode == 406 {
return .serverProvided return .serverProvided
@ -154,7 +161,7 @@ func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: App
} }
} }
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in |> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
account.stateManager.addUpdates(updates) stateManager?.addUpdates(updates)
return .complete() return .complete()
} }
} }
@ -164,10 +171,10 @@ public enum RestoreAppStoreReceiptError {
case generic case generic
} }
func _internal_canPurchasePremium(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> { func _internal_canPurchasePremium(postbox: Postbox, network: Network, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(account: account, purpose: purpose) return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> mapToSignal { purpose -> Signal<Bool, NoError> in |> mapToSignal { purpose -> Signal<Bool, NoError> in
return account.network.request(Api.functions.payments.canPurchasePremium(purpose: purpose)) return network.request(Api.functions.payments.canPurchaseStore(purpose: purpose))
|> map { result -> Bool in |> map { result -> Bool in
switch result { switch result {
case .boolTrue: case .boolTrue:

View File

@ -568,6 +568,21 @@ private final class StarsContextImpl {
self._state = state self._state = state
self._statePromise.set(.single(state)) self._statePromise.set(.single(state))
} }
var onUpdate: Signal<Void, NoError> {
return self._statePromise.get()
|> take(until: { value in
if let value {
if !value.flags.contains(.isPendingBalance) {
return SignalTakeAction(passthrough: true, complete: true)
}
}
return SignalTakeAction(passthrough: false, complete: false)
})
|> map { _ in
return Void()
}
}
} }
private extension StarsContext.State.Transaction { private extension StarsContext.State.Transaction {
@ -1011,6 +1026,18 @@ public final class StarsContext {
} }
} }
public var onUpdate: Signal<Void, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.onUpdate.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(account: Account) { init(account: Account) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {

View File

@ -39,11 +39,11 @@ public extension TelegramEngine {
} }
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> { public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_sendAppStoreReceipt(account: self.account, receipt: receipt, purpose: purpose) return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose)
} }
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> { public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(account: self.account, purpose: purpose) return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
} }
public func checkPremiumGiftCode(slug: String) -> Signal<PremiumGiftCodeInfo?, NoError> { public func checkPremiumGiftCode(slug: String) -> Signal<PremiumGiftCodeInfo?, NoError> {
@ -150,3 +150,21 @@ public extension TelegramEngine {
} }
} }
} }
public extension TelegramEngineUnauthorized {
final class Payments {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
}
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)
}
}
}

View File

@ -246,7 +246,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if let apiBot = connectedBots.first { if let apiBot = connectedBots.first {
switch apiBot { switch apiBot {
case let .connectedBot(flags, botId, recipients): case let .connectedBot(flags, botId, recipients, _):
mappedConnectedBot = TelegramAccountConnectedBot( mappedConnectedBot = TelegramAccountConnectedBot(
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)),
recipients: TelegramBusinessRecipients(apiValue: recipients), recipients: TelegramBusinessRecipients(apiValue: recipients),

View File

@ -30,7 +30,7 @@ public func secureIdPreparePhoneVerification(network: Network, value: SecureIdPh
switch sentCode { switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout): case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
return .single(SecureIdPreparePhoneVerificationPayload(type: SentAuthorizationCodeType(apiType: type), nextType: nextType.flatMap(AuthorizationCodeNextType.init), timeout: timeout, phone: value.phone, phoneCodeHash: phoneCodeHash)) return .single(SecureIdPreparePhoneVerificationPayload(type: SentAuthorizationCodeType(apiType: type), nextType: nextType.flatMap(AuthorizationCodeNextType.init), timeout: timeout, phone: value.phone, phoneCodeHash: phoneCodeHash))
case .sentCodeSuccess: case .sentCodeSuccess, .sentCodePaymentRequired:
return .never() return .never()
} }
} }

View File

@ -107,6 +107,14 @@ public final class TelegramEngineUnauthorized {
public lazy var localization: Localization = { public lazy var localization: Localization = {
return Localization(account: self.account) return Localization(account: self.account)
}() }()
public lazy var payments: Payments = {
return Payments(account: self.account)
}()
public lazy var itemCache: ItemCache = {
return ItemCache(account: self.account)
}()
} }
public enum SomeTelegramEngine { public enum SomeTelegramEngine {

View File

@ -81,7 +81,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String { public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator) var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator)
let fraction = Double(amount.nanos) / 10e6 let fraction = abs(Double(amount.nanos)) / 10e6
if fraction > 0.0 { if fraction > 0.0 {
balanceText.append(dateTimeFormat.decimalSeparator) balanceText.append(dateTimeFormat.decimalSeparator)
balanceText.append("\(Int32(fraction))") balanceText.append("\(Int32(fraction))")

View File

@ -466,6 +466,7 @@ swift_library(
"//submodules/TelegramUI/Components/ContentReportScreen", "//submodules/TelegramUI/Components/ContentReportScreen",
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen", "//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen",
"//third-party/recaptcha:RecaptchaEnterprise", "//third-party/recaptcha:RecaptchaEnterprise",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -193,6 +193,7 @@ private final class ScrollContent: CombinedComponent {
adsText = strings.AdsInfo_Bot_Ads_Text adsText = strings.AdsInfo_Bot_Ads_Text
infoRawText = strings.AdsInfo_Bot_Launch_Text infoRawText = strings.AdsInfo_Bot_Launch_Text
case .search: case .search:
//TODO:localize
respectText = "Ads like this do not use your personal information and are based on the search query you entered." respectText = "Ads like this do not use your personal information and are based on the search query you entered."
adsText = strings.AdsInfo_Bot_Ads_Text adsText = strings.AdsInfo_Bot_Ads_Text
infoRawText = "Anyone can create an ad to display in search results for any query. Check out the Telegram Ad Platform for details. [Learn More >]()" infoRawText = "Anyone can create an ad to display in search results for any query. Check out the Telegram Ad Platform for details. [Learn More >]()"

View File

@ -1787,7 +1787,7 @@ public final class ChatEmptyNode: ASDisplayNode {
isScheduledMessages = true isScheduledMessages = true
} }
let contentType: ChatEmptyNodeContentType var contentType: ChatEmptyNodeContentType
var displayAttachedDescription = false var displayAttachedDescription = false
switch subject { switch subject {
case .detailsPlaceholder: case .detailsPlaceholder:
@ -1815,7 +1815,7 @@ 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 { if let _ = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired contentType = .starsRequired
} else if interfaceState.isPremiumRequiredForMessaging { } else if interfaceState.isPremiumRequiredForMessaging {
contentType = .premiumRequired contentType = .premiumRequired

View File

@ -687,6 +687,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _ in
},
openAccountFreezeInfo: {
} }
) )
self.chatListNodeInteraction = chatListNodeInteraction self.chatListNodeInteraction = chatListNodeInteraction

View File

@ -299,7 +299,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} }
if isMediaInverted { if isMediaInverted {
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: 0) result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo ? 1 : 0)
} else { } else {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default))) result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
needReactions = false needReactions = false
@ -327,7 +327,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} }
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview { if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0) result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
} else { } else {
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} }
@ -362,7 +362,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
if result.isEmpty { if result.isEmpty {
needReactions = false needReactions = false
} }
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0) result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
} else { } else {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false needReactions = false

View File

@ -393,7 +393,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
let disclaimerText: NSMutableAttributedString let disclaimerText: NSMutableAttributedString
if let verification = item.verification { if let verification = item.verification {
disclaimerText = NSMutableAttributedString(string: " # \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor) disclaimerText = NSMutableAttributedString(string: " # \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor)
if let range = disclaimerText.string.range(of: "#") { if let range = disclaimerText.string.range(of: "#") {
disclaimerText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSRange(range, in: disclaimerText.string)) disclaimerText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSRange(range, in: disclaimerText.string))
disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string)) disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))

View File

@ -1237,6 +1237,11 @@ final class GiftOptionsScreenComponent: Component {
if availableProducts.isEmpty { if availableProducts.isEmpty {
var premiumProducts: [PremiumGiftProduct] = [] var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions { for option in premiumOptions {
if option.currency == "XTR" {
continue
}
let starsGiftOption = premiumOptions.first(where: { $0.currency == "XTR" && $0.months == option.months })
premiumProducts.append( premiumProducts.append(
PremiumGiftProduct( PremiumGiftProduct(
giftOption: CachedPremiumGiftOption( giftOption: CachedPremiumGiftOption(
@ -1246,7 +1251,7 @@ final class GiftOptionsScreenComponent: Component {
botUrl: "", botUrl: "",
storeProductId: option.storeProductId storeProductId: option.storeProductId
), ),
starsGiftOption: nil, starsGiftOption: starsGiftOption,
storeProduct: nil, storeProduct: nil,
discount: nil discount: nil
) )

View File

@ -498,16 +498,8 @@ final class GiftSetupScreenComponent: Component {
starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.state let _ = (starsContext.onUpdate
|> take(until: { value in |> deliverOnMainQueue).start(next: {
if let value {
if !value.flags.contains(.isPendingBalance) {
return SignalTakeAction(passthrough: true, complete: true)
}
}
return SignalTakeAction(passthrough: false, complete: false)
})
|> deliverOnMainQueue).start(next: { _ in
proceed() proceed()
}) })
} }

View File

@ -383,10 +383,14 @@ private final class GiftViewSheetContent: CombinedComponent {
options: options ?? [], options: options ?? [],
purpose: .upgradeStarGift(requiredStars: price), purpose: .upgradeStarGift(requiredStars: price),
completion: { [weak starsContext] stars in completion: { [weak starsContext] stars in
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0)) guard let starsContext else {
Queue.mainQueue().after(2.0) { return
proceed(upgradeForm.id)
} }
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
proceed(upgradeForm.id)
})
} }
) )
controller.push(purchaseController) controller.push(purchaseController)

View File

@ -188,6 +188,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
let items = (0 ..< 1).map { _ -> ChatListItem in let items = (0 ..< 1).map { _ -> ChatListItem in
@ -548,6 +549,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _ in
},
openAccountFreezeInfo: {
} }
) )

View File

@ -998,7 +998,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}))) })))
} }
if canReorder { if case .unique = gift.gift, canReorder {
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
c?.dismiss(completion: { [weak self] in c?.dismiss(completion: { [weak self] in
guard let self else { guard let self else {

View File

@ -0,0 +1,36 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AccountFreezeInfoScreen",
module_name = "AccountFreezeInfoScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/Components/SheetComponent",
"//submodules/PresentationDataUtils",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Markdown",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,578 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import Markdown
import TextFormat
import TelegramPresentationData
import TelegramStringFormatting
import ViewControllerComponent
import SheetComponent
import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent
import LottieComponent
import ButtonComponent
import AccountContext
private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let configuration: AccountFreezeConfiguration
let submitAppeal: () -> Void
let dismiss: () -> Void
init(
context: AccountContext,
configuration: AccountFreezeConfiguration,
submitAppeal: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.configuration = configuration
self.submitAppeal = submitAppeal
self.dismiss = dismiss
}
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class State: ComponentState {
var cachedChevronImage: (UIImage, PresentationTheme)?
var cachedCloseImage: (UIImage, PresentationTheme)?
let playOnce = ActionSlot<Void>()
private var didPlayAnimation = false
func playAnimationIfNeeded() {
guard !self.didPlayAnimation else {
return
}
self.didPlayAnimation = true
self.playOnce.invoke(Void())
}
}
func makeState() -> State {
return State()
}
static var body: Body {
let animation = Child(LottieComponent.self)
let title = Child(BalancedTextComponent.self)
let list = Child(List<Empty>.self)
let actionButton = Child(ButtonComponent.self)
let closeButton = Child(ButtonComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let component = context.component
let state = context.state
let theme = environment.theme
let strings = environment.strings
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
let titleFont = Font.semibold(20.0)
let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
let spacing: CGFloat = 16.0
var contentSize = CGSize(width: context.availableSize.width, height: 32.0)
let animationHeight: CGFloat = 120.0
let animation = animation.update(
component: LottieComponent(
content: LottieComponent.AppBundleContent(name: "Banned"),
startingPosition: .begin,
playOnce: state.playOnce
),
environment: {},
availableSize: CGSize(width: animationHeight, height: animationHeight),
transition: .immediate
)
context.add(animation
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + animation.size.height / 2.0))
)
contentSize.height += animation.size.height
contentSize.height += spacing + 5.0
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Your Account is Frozen", font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
)
contentSize.height += title.size.height
contentSize.height += spacing - 2.0
//TODO:localize
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "ads",
component: AnyComponent(ParagraphComponent(
title: "Violation of Terms",
titleColor: textColor,
text: "Your account was frozen for breaking Telegram's Terms and Conditions.",
textColor: secondaryTextColor,
iconName: "Account Freeze/Violation",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "split",
component: AnyComponent(ParagraphComponent(
title: "Read-Only Mode",
titleColor: textColor,
text: "You can access your account but can't send messages or take actions.",
textColor: secondaryTextColor,
iconName: "Ads/Privacy",
iconColor: linkColor
))
)
)
let dateString = stringForFullDate(timestamp: component.configuration.freezeUntilDate ?? 0, strings: strings, dateTimeFormat: environment.dateTimeFormat)
items.append(
AnyComponentWithIdentity(
id: "withdrawal",
component: AnyComponent(ParagraphComponent(
title: "Appeal Before Deactivation",
titleColor: textColor,
text: "Appeal via [@SpamBot]() before \(dateString), or your account will be deleted.",
textColor: secondaryTextColor,
iconName: "Account Freeze/Appeal",
iconColor: linkColor,
action: {
component.submitAppeal()
Queue.mainQueue().after(1.0) {
component.dismiss()
}
}
))
)
)
let list = list.update(
component: List(items),
availableSize: CGSize(width: context.availableSize.width - sideInset, height: 10000.0),
transition: context.transition
)
context.add(list
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))
)
contentSize.height += list.size.height
contentSize.height += spacing + 2.0
let buttonAttributedString = NSMutableAttributedString(string: "Submit an Appeal", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let actionButton = actionButton.update(
component: ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
),
isEnabled: true,
displaysProgress: false,
action: {
component.submitAppeal()
Queue.mainQueue().after(1.0) {
component.dismiss()
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(actionButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
.cornerRadius(10.0)
)
contentSize.height += actionButton.size.height
contentSize.height += 8.0
let closeAttributedString = NSMutableAttributedString(string: "Understood", font: Font.regular(17.0), textColor: environment.theme.list.itemCheckColors.fillColor, paragraphAlignment: .center)
let closeButton = closeButton.update(
component: ButtonComponent(
background: ButtonComponent.Background(
color: .clear,
foreground: .clear,
pressedColor: .clear,
cornerRadius: 10.0
),
content: AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(MultilineTextComponent(text: .plain(closeAttributedString)))
),
isEnabled: true,
displaysProgress: false,
action: {
component.dismiss()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(closeButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
)
contentSize.height += closeButton.size.height
if environment.safeInsets.bottom > 0 {
contentSize.height += environment.safeInsets.bottom + 5.0
} else {
contentSize.height += 12.0
}
state.playAnimationIfNeeded()
return contentSize
}
}
}
private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let configuration: AccountFreezeConfiguration
let submitAppeal: () -> Void
init(
context: AccountContext,
configuration: AccountFreezeConfiguration,
submitAppeal: @escaping () -> Void
) {
self.context = context
self.configuration = configuration
self.submitAppeal = submitAppeal
}
static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context,
configuration: context.component.configuration,
submitAppeal: context.component.submitAppeal,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
externalState: sheetExternalState,
animateOut: animateOut
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else {
if let controller = controller() {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
}
public final class AccountFreezeInfoScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(
context: AccountContext
) {
self.context = context
let configuration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
var submitAppealImpl: (() -> Void)?
super.init(
context: context,
component: SheetContainerComponent(
context: context,
configuration: configuration,
submitAppeal: {
submitAppealImpl?()
}
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
submitAppealImpl = { [weak self] in
guard let self, let url = configuration.freezeAppealUrl else {
return
}
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: self.navigationController as? NavigationController, dismissInput: {})
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}
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
}
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 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 title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.semibold(15.0),
textColor: component.titleColor,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = component.textColor
let linkColor = component.iconColor
let markdownAttributes = 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: markdownAttributes),
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)
}
}
}
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(foregroundColor.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})
}

View File

@ -213,6 +213,8 @@ final class GreetingMessageListItemComponent: Component {
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _ in
},
openAccountFreezeInfo: {
} }
) )
self.chatListNodeInteraction = chatListNodeInteraction self.chatListNodeInteraction = chatListNodeInteraction

View File

@ -234,6 +234,8 @@ final class QuickReplySetupScreenComponent: Component {
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _ in
},
openAccountFreezeInfo: {
} }
) )

View File

@ -876,6 +876,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -589,10 +589,15 @@ private final class SheetContent: CombinedComponent {
options: state?.options ?? [], options: state?.options ?? [],
purpose: purpose, purpose: purpose,
completion: { [weak starsContext] stars in completion: { [weak starsContext] stars in
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0)) guard let starsContext else {
Queue.mainQueue().after(0.1) { return
completion()
} }
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
completion()
})
} }
) )
controller?.push(purchaseController) controller?.push(purchaseController)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "admock.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -294,7 +294,7 @@ public final class AccountContextImpl: AccountContext {
self.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData) self.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData)
self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account) self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account)
self.inAppPurchaseManager = InAppPurchaseManager(engine: self.engine) self.inAppPurchaseManager = InAppPurchaseManager(engine: .authorized(self.engine))
self.starsContext = self.engine.payments.peerStarsContext() self.starsContext = self.engine.payments.peerStarsContext()
} else { } else {
self.prefetchManager = nil self.prefetchManager = nil
@ -356,8 +356,9 @@ public final class AccountContextImpl: AccountContext {
self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes())) self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes()))
if !temp { if !temp {
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
let currentCountriesConfiguration = self.currentCountriesConfiguration let currentCountriesConfiguration = self.currentCountriesConfiguration
self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: nil) self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: langCode)
|> deliverOnMainQueue).start(next: { value in |> deliverOnMainQueue).start(next: { value in
let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value)) let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value))
}) })

View File

@ -4412,7 +4412,18 @@ extension ChatControllerImpl {
}, openMessagePayment: { }, openMessagePayment: {
}, openBoostToUnrestrict: { [weak self] in }, openBoostToUnrestrict: { [weak self] in
guard let self, let peerId = self.chatLocation.peerId, let cachedData = self.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else { guard let self else {
return
}
let accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if let _ = accountFreezeConfiguration.freezeUntilDate {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
return
}
guard let peerId = self.chatLocation.peerId, let cachedData = self.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else {
return return
} }

View File

@ -72,7 +72,10 @@ extension ChatControllerImpl {
return return
} }
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { _ in let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { _ in
completion(false) let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
completion(false)
})
}) })
self.push(controller) self.push(controller)
}) })

View File

@ -21,6 +21,18 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
return (nil, nil) return (nil, nil)
} }
let accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if let _ = accountFreezeConfiguration.freezeUntilDate {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
let panel = ChatRestrictedInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)
}
}
if let _ = chatPresentationInterfaceState.search { if let _ = chatPresentationInterfaceState.search {
var selectionPanel: ChatMessageSelectionInputPanelNode? var selectionPanel: ChatMessageSelectionInputPanelNode?
if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState { if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState {

View File

@ -583,11 +583,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
} }
} }
/*#if DEBUG if let emojiStatus = emojiStatus, case .emoji = emojiStatus.content {
emojiStatus = PeerEmojiStatus(fileId: 5062172592505356289, expirationDate: nil)
#endif*/
if let emojiStatus = emojiStatus {
self.emojiSeparatorNode.isHidden = false self.emojiSeparatorNode.isHidden = false
transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel))) transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel)))

View File

@ -9,10 +9,12 @@ import TelegramStringFormatting
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import TelegramPresentationData import TelegramPresentationData
import ChatInputPanelNode import ChatInputPanelNode
import AccountContext
final class ChatRestrictedInputPanelNode: ChatInputPanelNode { final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private var iconView: UIImageView? private var iconView: UIImageView?
private var presentationInterfaceState: ChatPresentationInterfaceState? private var presentationInterfaceState: ChatPresentationInterfaceState?
@ -22,12 +24,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.textNode.maximumNumberOfLines = 2 self.textNode.maximumNumberOfLines = 2
self.textNode.textAlignment = .center self.textNode.textAlignment = .center
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.textAlignment = .center
self.buttonNode = HighlightTrackingButtonNode() self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isUserInteractionEnabled = false self.buttonNode.isUserInteractionEnabled = false
super.init() super.init()
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in self.buttonNode.highligthedChanged = { [weak self] highlighted in
@ -37,11 +44,15 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.iconView?.alpha = 0.4 self.iconView?.alpha = 0.4
self.textNode.layer.removeAnimation(forKey: "opacity") self.textNode.layer.removeAnimation(forKey: "opacity")
self.textNode.alpha = 0.4 self.textNode.alpha = 0.4
self.subtitleNode.layer.removeAnimation(forKey: "opacity")
self.subtitleNode.alpha = 0.4
} else { } else {
self.iconView?.alpha = 1.0 self.iconView?.alpha = 1.0
self.iconView?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) self.iconView?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode.alpha = 1.0 self.textNode.alpha = 1.0
self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
} }
} }
} }
@ -74,7 +85,16 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
var iconSpacing: CGFloat = 4.0 var iconSpacing: CGFloat = 4.0
var isUserInteractionEnabled = false var isUserInteractionEnabled = false
if case let .replyThread(message) = interfaceState.chatLocation, message.peerId == self.context?.account.peerId { var accountFreezeConfiguration: AccountFreezeConfiguration?
if let context = self.context {
accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
}
//TODO:localize
if let _ = accountFreezeConfiguration?.freezeUntilDate {
self.textNode.attributedText = NSAttributedString(string: "You account is frozen", font: Font.semibold(15.0), textColor: interfaceState.theme.list.itemDestructiveColor)
self.subtitleNode.attributedText = NSAttributedString(string: "Tap to view details", font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
isUserInteractionEnabled = true
} else if case let .replyThread(message) = interfaceState.chatLocation, message.peerId == self.context?.account.peerId {
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelStatusAuthorHidden, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelStatusAuthorHidden, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
} else if let threadData = interfaceState.threadData, threadData.isClosed { } else if let threadData = interfaceState.threadData, threadData.isClosed {
iconImage = PresentationResourcesChat.chatPanelLockIcon(interfaceState.theme) iconImage = PresentationResourcesChat.chatPanelLockIcon(interfaceState.theme)
@ -116,6 +136,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
let panelHeight = defaultHeight(metrics: metrics) let panelHeight = defaultHeight(metrics: metrics)
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
var originX: CGFloat = leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0) var originX: CGFloat = leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0)
@ -139,10 +160,18 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
iconView.removeFromSuperview() iconView.removeFromSuperview()
} }
let textFrame = CGRect(origin: CGPoint(x: originX, y: floor((panelHeight - textSize.height) / 2.0)), size: textSize) var combinedHeight: CGFloat = textSize.height
if subtitleSize.height > 0.0 {
combinedHeight += subtitleSize.height + 2.0
}
let textFrame = CGRect(origin: CGPoint(x: originX, y: floor((panelHeight - combinedHeight) / 2.0)), size: textSize)
self.textNode.frame = textFrame self.textNode.frame = textFrame
self.buttonNode.frame = textFrame.insetBy(dx: -8.0, dy: -12.0) let subtitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - subtitleSize.width) / 2.0), y: floor((panelHeight + combinedHeight) / 2.0) - subtitleSize.height), size: subtitleSize)
self.subtitleNode.frame = subtitleFrame
let combinedFrame = textFrame.union(subtitleFrame)
self.buttonNode.frame = combinedFrame.insetBy(dx: -8.0, dy: -12.0)
return panelHeight return panelHeight
} }

View File

@ -296,6 +296,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _ in
}, openAccountFreezeInfo: {
}) })
interaction.searchTextHighightState = searchQuery interaction.searchTextHighightState = searchQuery
self.interaction = interaction self.interaction = interaction

View File

@ -183,6 +183,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _ in
},
openAccountFreezeInfo: {
} }
) )

View File

@ -77,6 +77,7 @@ import ContentReportScreen
import AffiliateProgramSetupScreen import AffiliateProgramSetupScreen
import GalleryUI import GalleryUI
import ShareController import ShareController
import AccountFreezeInfoScreen
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() let subscribers = Bag<(Bool) -> Void>()
@ -3469,6 +3470,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
return controller return controller
} }
public func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController {
return AccountFreezeInfoScreen(context: context)
}
} }
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {