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 makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
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 {
}

View File

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

View File

@ -20,6 +20,7 @@ import TelegramNotices
import AuthenticationServices
import Markdown
import AlertUI
import InAppPurchaseManager
import ObjectiveC
private var ObjCKey_Delegate: Int?
@ -59,6 +60,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
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) {
self.sharedContext = sharedContext
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)
self.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(self.engine))
self.stateDisposable = (self.engine.auth.state()
|> map { state -> InnerState in
if case .authorized = state {
@ -758,6 +763,18 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
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, *)
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
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))
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
guard let self else {
return

View File

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

View File

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

View File

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

View File

@ -115,6 +115,7 @@ public final class ChatListNodeInteraction {
let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void
let openAdInfo: (ASDisplayNode) -> Void
let openAccountFreezeInfo: () -> Void
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
@ -173,7 +174,8 @@ public final class ChatListNodeInteraction {
editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void,
openAdInfo: @escaping (ASDisplayNode) -> Void
openAdInfo: @escaping (ASDisplayNode) -> Void,
openAccountFreezeInfo: @escaping () -> Void
) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
@ -220,6 +222,7 @@ public final class ChatListNodeInteraction {
self.openWebApp = openWebApp
self.openPhotoSetup = openPhotoSetup
self.openAdInfo = openAdInfo
self.openAccountFreezeInfo = openAccountFreezeInfo
}
}
@ -770,6 +773,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -1116,6 +1121,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -1239,6 +1246,7 @@ public final class ChatListNode: ListView {
public var openWebApp: ((TelegramUser) -> Void)?
public var openPhotoSetup: (() -> Void)?
public var openAdInfo: ((ASDisplayNode) -> Void)?
public var openAccountFreezeInfo: (() -> Void)?
private var theme: PresentationTheme
@ -1899,6 +1907,8 @@ public final class ChatListNode: ListView {
self.openPhotoSetup?()
}, openAdInfo: { [weak self] node in
self?.openAdInfo?(node)
}, openAccountFreezeInfo: { [weak self] in
self?.openAccountFreezeInfo?()
})
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 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(
context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(),
@ -1998,11 +2018,12 @@ public final class ChatListNode: ListView {
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
),
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
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
}
@ -2035,8 +2056,10 @@ public final class ChatListNode: ListView {
if dismissedSuggestions.contains(.todayBirthdays) {
todayBirthdayPeerIds = []
}
if suggestions.contains(.starsSubscriptionLowBalance) {
if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in

View File

@ -92,6 +92,7 @@ public enum ChatListNotice: Equatable {
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer)
case accountFreeze
}
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)
textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
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

View File

@ -151,8 +151,8 @@ open class ViewControllerComponentContainer: ViewController {
private var currentIsVisible: Bool = false
private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
init(context: AccountContext, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
init(presentationData: PresentationData, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) {
self.presentationData = presentationData
self.controller = controller
@ -234,7 +234,7 @@ open class ViewControllerComponentContainer: ViewController {
return self.displayNode as! Node
}
private let context: AccountContext
private var presentationData: PresentationData
private var theme: Theme
public private(set) var component: AnyComponent<ViewControllerComponentContainer.Environment>
@ -252,17 +252,19 @@ open class ViewControllerComponentContainer: ViewController {
theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil
) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component)
self.theme = theme
let presentationData: PresentationData
var effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
if let updatedPresentationData {
presentationData = updatedPresentationData.initial
effectiveUpdatedPresentationData = updatedPresentationData
} 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?
switch navigationBarAppearance {
case .none:
@ -274,7 +276,47 @@ open class ViewControllerComponentContainer: ViewController {
}
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
if let strongSelf = self {
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() {
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()
}

View File

@ -591,7 +591,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
allSelected = false
}
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()
}
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
}
private let engine: TelegramEngine
private let engine: SomeTelegramEngine
private var products: [Product] = []
private var productsPromise = Promise<[Product]>([])
@ -231,7 +231,9 @@ public final class InAppPurchaseManager: NSObject {
private let disposableSet = DisposableDict<String>()
public init(engine: TelegramEngine) {
private var lastRequestTimestamp: Double?
public init(engine: SomeTelegramEngine) {
self.engine = engine
super.init()
@ -255,11 +257,15 @@ public final class InAppPurchaseManager: NSObject {
productRequest.start()
self.productRequest = productRequest
self.lastRequestTimestamp = CFAbsoluteTimeGetCurrent()
}
public var availableProducts: Signal<[Product], NoError> {
if self.products.isEmpty && self.productRequest == nil {
self.requestProducts()
if self.products.isEmpty {
if let lastRequestTimestamp, CFAbsoluteTimeGetCurrent() - lastRequestTimestamp > 10.0 {
Logger.shared.log("InAppPurchaseManager", "No available products, rerequest")
self.requestProducts()
}
}
return self.productsPromise.get()
}
@ -287,7 +293,13 @@ public final class InAppPurchaseManager: NSObject {
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)")
@ -399,7 +411,13 @@ private func getReceiptData() -> Data? {
extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
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
@ -519,7 +537,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
(purpose
|> castError(AssignAppStoreTransactionError.self)
|> 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
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transactions [\(transactionIds)] failed to assign")
for transaction in transactions {
@ -551,8 +574,15 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
self.onRestoreCompletion = nil
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.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore).start(error: { error in
signal.start(error: { error in
Queue.mainQueue().async {
if case .serverProvided = error {
onRestoreCompletion(.succeed(true))
@ -586,14 +616,17 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
}
private func debugSaveReceipt(receiptData: Data) {
guard case let .authorized(engine) = self.engine else {
return
}
let id = Int64.random(in: Int64.min ... Int64.max)
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 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 text
case entities
case restore
case phoneNumber
case phoneCodeHash
}
enum PurposeType: Int32 {
@ -637,6 +673,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars
case starsGift
case starsGiveaway
case authCode
}
case subscription
@ -648,6 +685,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars(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 authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String)
public init(from decoder: Decoder) throws {
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),
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:
throw DecodingError.generic
}
@ -757,6 +801,11 @@ private final class PendingInAppPurchaseState: Codable {
try container.encode(randomId, forKey: .randomId)
try container.encode(untilDate, forKey: .untilDate)
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)
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)
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)
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)
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)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key))
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
switch engine {
case let .authorized(engine):
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)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
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)
switch engine {
case let .authorized(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)
}
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;
}
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)
request.errorContext = [[MTRequestErrorContext alloc] init];

View File

@ -233,6 +233,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}, openWebApp: { _ in
}, openPhotoSetup: {
}, 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)

View File

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

View File

@ -84,7 +84,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1778593322] = { return Api.BotApp.parse_botApp($0) }
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($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[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($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[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($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[-451302485] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($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[-1390068360] = { return Api.CodeSettings.parse_codeSettings($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[341499403] = { return Api.Contact.parse_contact($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[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($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[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($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[969307186] = { return Api.Update.parse_updateSavedReactionTags($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[-337352679] = { return Api.Update.parse_updateServiceNotification($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[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) }
dict[304435204] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) }
dict[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) }
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }
@ -1589,6 +1593,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRecipients:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRights:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessChatLink:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessGreetingMessage:

View File

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

View File

@ -116,6 +116,7 @@ public extension Api {
}
public extension Api {
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 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)
@ -126,6 +127,16 @@ public extension Api {
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
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):
if boxed {
buffer.appendInt32(1634697192)
@ -223,6 +234,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
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):
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):
@ -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? {
var _1: Api.InputUser?
if let signature = reader.readInt32() {

View File

@ -1103,6 +1103,7 @@ public extension Api {
case updateSavedGifs
case updateSavedReactionTags
case updateSavedRingtones
case updateSentPhoneCode(sentCode: Api.auth.SentCode)
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 updateSmsJob(jobId: String)
@ -2198,6 +2199,12 @@ public extension Api {
buffer.appendInt32(1960361625)
}
break
case .updateSentPhoneCode(let sentCode):
if boxed {
buffer.appendInt32(1347068303)
}
sentCode.serialize(buffer, true)
break
case .updateSentStoryReaction(let peer, let storyId, let reaction):
if boxed {
@ -2602,6 +2609,8 @@ public extension Api {
return ("updateSavedReactionTags", [])
case .updateSavedRingtones:
return ("updateSavedRingtones", [])
case .updateSentPhoneCode(let sentCode):
return ("updateSentPhoneCode", [("sentCode", sentCode as Any)])
case .updateSentStoryReaction(let peer, let storyId, let reaction):
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):
@ -4803,6 +4812,19 @@ public extension Api {
public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? {
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? {
var _1: Api.Peer?
if let signature = reader.readInt32() {

View File

@ -567,6 +567,7 @@ public extension Api.auth {
public extension Api.auth {
enum SentCode: TypeConstructorDescription {
case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?)
case sentCodePaymentRequired(storeProduct: String)
case sentCodeSuccess(authorization: Api.auth.Authorization)
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 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .sentCodePaymentRequired(let storeProduct):
if boxed {
buffer.appendInt32(304435204)
}
serializeString(storeProduct, buffer: buffer, boxed: false)
break
case .sentCodeSuccess(let authorization):
if boxed {
buffer.appendInt32(596704836)
@ -594,6 +601,8 @@ public extension Api.auth {
switch self {
case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout):
return ("sentCode", [("flags", flags as Any), ("type", type as Any), ("phoneCodeHash", phoneCodeHash as Any), ("nextType", nextType as Any), ("timeout", timeout as Any)])
case .sentCodePaymentRequired(let storeProduct):
return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any)])
case .sentCodeSuccess(let authorization):
return ("sentCodeSuccess", [("authorization", authorization as Any)])
}
@ -626,6 +635,17 @@ public extension Api.auth {
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? {
var _1: Api.auth.Authorization?
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 {
enum BusinessChatLink: TypeConstructorDescription {
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 {
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()
buffer.appendInt32(1138250269)
buffer.appendInt32(1721797758)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {rights!.serialize(buffer, true)}
bot.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)
var result: Api.Updates?
if let signature = reader.readInt32() {
@ -9063,11 +9064,11 @@ 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()
buffer.appendInt32(-1614700874)
buffer.appendInt32(1339842215)
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)
var result: Api.Bool?
if let signature = reader.readInt32() {

View File

@ -664,25 +664,26 @@ public extension Api {
}
public extension Api {
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) {
switch self {
case .connectedBot(let flags, let botId, let recipients):
case .connectedBot(let flags, let botId, let recipients, let rights):
if boxed {
buffer.appendInt32(-1123645951)
buffer.appendInt32(-849058964)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
recipients.serialize(buffer, true)
rights.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .connectedBot(let flags, let botId, let recipients):
return ("connectedBot", [("flags", flags as Any), ("botId", botId as Any), ("recipients", recipients as Any)])
case .connectedBot(let flags, let botId, let recipients, let rights):
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() {
_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 _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!)
}
else {
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)))
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):
switch authorization {
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)))
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))
case .sentCodeSuccess:
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)))
case let .sentCodePaymentRequired(storeProduct):
//TODO:release
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: "", storeProduct: storeProduct)))
case .sentCodeSuccess:
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)))
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:
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)))
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()
case .sentCodeSuccess:
return .complete()

View File

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

View File

@ -139,6 +139,7 @@ private enum UnauthorizedAccountStateContentsValue: Int32 {
case signUp = 5
case passwordRecovery = 6
case awaitingAccountReset = 7
case payment = 8
}
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 awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool)
case signUp(number: String, codeHash: String, firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, syncContacts: Bool)
case payment(number: String, codeHash: String, storeProduct: String)
public init(decoder: PostboxDecoder) {
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)
case UnauthorizedAccountStateContentsValue.signUp.rawValue:
self = .signUp(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), firstName: decoder.decodeStringForKey("f", orElse: ""), lastName: decoder.decodeStringForKey("l", orElse: ""), termsOfService: decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
case UnauthorizedAccountStateContentsValue.payment.rawValue:
self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""))
default:
assertionFailure()
self = .empty
@ -303,6 +308,11 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
encoder.encodeNil(forKey: "tos")
}
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 {
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 {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
@ -188,7 +188,7 @@ private func internalResendChangeAccountPhoneNumberVerification(account: Account
} else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}

View File

@ -34,7 +34,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) ->
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
@ -57,7 +57,7 @@ func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
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
}
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)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)

View File

@ -20,9 +20,10 @@ public enum AppStoreTransactionPurpose {
case stars(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 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 {
case .subscription, .upgrade, .restore:
var flags: Int32 = 0
@ -36,7 +37,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
}
return .single(.inputStorePaymentPremiumSubscription(flags: flags))
case let .gift(peerId, currency, amount):
return account.postbox.loadedPeerWithId(peerId)
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
@ -44,7 +45,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
}
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 apiBoostPeer: Api.InputPeer?
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)
}
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 {
return .complete()
}
@ -101,7 +102,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
case let .stars(count, currency, amount):
return .single(.inputStorePaymentStarsTopup(stars: count, currency: currency, amount: amount))
case let .starsGift(peerId, count, currency, amount):
return account.postbox.loadedPeerWithId(peerId)
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
@ -109,7 +110,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
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):
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 {
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))
}
|> 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> {
return apiInputStorePaymentPurpose(account: account, purpose: purpose)
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager?, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
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
if error.errorCode == 406 {
return .serverProvided
@ -154,7 +161,7 @@ func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: App
}
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
account.stateManager.addUpdates(updates)
stateManager?.addUpdates(updates)
return .complete()
}
}
@ -164,10 +171,10 @@ public enum RestoreAppStoreReceiptError {
case generic
}
func _internal_canPurchasePremium(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(account: account, purpose: purpose)
func _internal_canPurchasePremium(postbox: Postbox, network: Network, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> 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
switch result {
case .boolTrue:

View File

@ -568,6 +568,21 @@ private final class StarsContextImpl {
self._state = 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 {
@ -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) {
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> {
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> {
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> {
@ -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 {
switch apiBot {
case let .connectedBot(flags, botId, recipients):
case let .connectedBot(flags, botId, recipients, _):
mappedConnectedBot = TelegramAccountConnectedBot(
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)),
recipients: TelegramBusinessRecipients(apiValue: recipients),

View File

@ -30,7 +30,7 @@ public func secureIdPreparePhoneVerification(network: Network, value: SecureIdPh
switch sentCode {
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))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}

View File

@ -107,6 +107,14 @@ public final class TelegramEngineUnauthorized {
public lazy var localization: Localization = {
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 {

View File

@ -81,7 +81,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
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 {
balanceText.append(dateTimeFormat.decimalSeparator)
balanceText.append("\(Int32(fraction))")

View File

@ -466,6 +466,7 @@ swift_library(
"//submodules/TelegramUI/Components/ContentReportScreen",
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen",
"//third-party/recaptcha:RecaptchaEnterprise",
] + select({
"@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
infoRawText = strings.AdsInfo_Bot_Launch_Text
case .search:
//TODO:localize
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
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
}
let contentType: ChatEmptyNodeContentType
var contentType: ChatEmptyNodeContentType
var displayAttachedDescription = false
switch subject {
case .detailsPlaceholder:
@ -1815,7 +1815,7 @@ public final class ChatEmptyNode: ASDisplayNode {
} else if let _ = interfaceState.peerNearbyData {
contentType = .peerNearby
} else if let peer = peer as? TelegramUser {
if let _ = interfaceState.sendPaidMessageStars {
if let _ = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired
} else if interfaceState.isPremiumRequiredForMessaging {
contentType = .premiumRequired

View File

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

View File

@ -299,7 +299,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
}
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 {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
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 {
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 {
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 {
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 {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false

View File

@ -393,7 +393,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
let disclaimerText: NSMutableAttributedString
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: "#") {
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))

View File

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

View File

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

View File

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

View File

@ -188,6 +188,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
let items = (0 ..< 1).map { _ -> ChatListItem in
@ -548,6 +549,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
openPhotoSetup: {
},
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
c?.dismiss(completion: { [weak self] in
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: {
},
openAdInfo: { _ in
},
openAccountFreezeInfo: {
}
)
self.chatListNodeInteraction = chatListNodeInteraction

View File

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

View File

@ -876,6 +876,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}, openWebApp: { _ in
}, openPhotoSetup: {
}, 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)

View File

@ -589,10 +589,15 @@ private final class SheetContent: CombinedComponent {
options: state?.options ?? [],
purpose: purpose,
completion: { [weak starsContext] stars in
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
Queue.mainQueue().after(0.1) {
completion()
guard let starsContext else {
return
}
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
completion()
})
}
)
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.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()
} else {
self.prefetchManager = nil
@ -356,8 +356,9 @@ public final class AccountContextImpl: AccountContext {
self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes()))
if !temp {
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
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
let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value))
})

View File

@ -4412,7 +4412,18 @@ extension ChatControllerImpl {
}, openMessagePayment: {
}, 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
}

View File

@ -72,7 +72,10 @@ extension ChatControllerImpl {
return
}
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)
})

View File

@ -21,6 +21,18 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
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 {
var selectionPanel: ChatMessageSelectionInputPanelNode?
if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState {

View File

@ -583,11 +583,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
}
}
/*#if DEBUG
emojiStatus = PeerEmojiStatus(fileId: 5062172592505356289, expirationDate: nil)
#endif*/
if let emojiStatus = emojiStatus {
if let emojiStatus = emojiStatus, case .emoji = emojiStatus.content {
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)))

View File

@ -9,10 +9,12 @@ import TelegramStringFormatting
import ChatPresentationInterfaceState
import TelegramPresentationData
import ChatInputPanelNode
import AccountContext
final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
private let buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private var iconView: UIImageView?
private var presentationInterfaceState: ChatPresentationInterfaceState?
@ -22,12 +24,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.textNode.maximumNumberOfLines = 2
self.textNode.textAlignment = .center
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.textAlignment = .center
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
@ -37,11 +44,15 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.iconView?.alpha = 0.4
self.textNode.layer.removeAnimation(forKey: "opacity")
self.textNode.alpha = 0.4
self.subtitleNode.layer.removeAnimation(forKey: "opacity")
self.subtitleNode.alpha = 0.4
} else {
self.iconView?.alpha = 1.0
self.iconView?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode.alpha = 1.0
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 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)
} else if let threadData = interfaceState.threadData, threadData.isClosed {
iconImage = PresentationResourcesChat.chatPanelLockIcon(interfaceState.theme)
@ -116,6 +136,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
let panelHeight = defaultHeight(metrics: metrics)
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)
@ -139,10 +160,18 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
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.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
}

View File

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

View File

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

View File

@ -77,6 +77,7 @@ import ContentReportScreen
import AffiliateProgramSetupScreen
import GalleryUI
import ShareController
import AccountFreezeInfoScreen
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@ -3469,6 +3470,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
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? {