mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Various improvements
This commit is contained in:
parent
81f179be9d
commit
29aff4a8a6
@ -8,6 +8,7 @@ telegram_is_internal_build = "false"
|
||||
telegram_is_appstore_build = "true"
|
||||
telegram_appstore_id = "686449807"
|
||||
telegram_app_specific_url_scheme = "tg"
|
||||
telegram_premium_iap_product_id = ""
|
||||
telegram_aps_environment = "production"
|
||||
telegram_enable_siri = True
|
||||
telegram_enable_icloud = True
|
||||
|
@ -35,6 +35,7 @@ prepare_build_variables () {
|
||||
IS_APPSTORE_BUILD \
|
||||
APPSTORE_ID \
|
||||
APP_SPECIFIC_URL_SCHEME \
|
||||
PREMIUM_IAP_PRODUCT_ID \
|
||||
TELEGRAM_DISABLE_EXTENSIONS \
|
||||
)
|
||||
|
||||
@ -66,6 +67,7 @@ prepare_build_variables () {
|
||||
echo "telegram_is_appstore_build = \"$IS_APPSTORE_BUILD\"" >> "$VARIABLES_PATH"
|
||||
echo "telegram_appstore_id = \"$APPSTORE_ID\"" >> "$VARIABLES_PATH"
|
||||
echo "telegram_app_specific_url_scheme = \"$APP_SPECIFIC_URL_SCHEME\"" >> "$VARIABLES_PATH"
|
||||
echo "telegram_premium_iap_product_id = \"$PREMIUM_IAP_PRODUCT_ID\"" >> "$VARIABLES_PATH"
|
||||
echo "telegram_aps_environment = \"$APS_ENVIRONMENT\"" >> "$VARIABLES_PATH"
|
||||
|
||||
if [ "$TELEGRAM_DISABLE_EXTENSIONS" == "1" ]; then
|
||||
|
@ -15,6 +15,7 @@ export IS_INTERNAL_BUILD="false"
|
||||
export IS_APPSTORE_BUILD="true"
|
||||
export APPSTORE_ID="686449807"
|
||||
export APP_SPECIFIC_URL_SCHEME="tgapp"
|
||||
export PREMIUM_IAP_PRODUCT_ID="org.telegram.telegramPremium.monthly"
|
||||
|
||||
if [ -z "$BUILD_NUMBER" ]; then
|
||||
echo "BUILD_NUMBER is not defined"
|
||||
|
@ -22,6 +22,7 @@ swift_library(
|
||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -11,6 +11,7 @@ import Display
|
||||
import DeviceLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
import MeshAnimationCache
|
||||
import InAppPurchaseManager
|
||||
|
||||
public final class TelegramApplicationOpenUrlCompletion {
|
||||
public let completion: (Bool) -> Void
|
||||
@ -654,6 +655,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
var locationManager: DeviceLocationManager? { get }
|
||||
var callManager: PresentationCallManager? { get }
|
||||
var contactDataManager: DeviceContactDataManager? { get }
|
||||
var inAppPurchaseManager: InAppPurchaseManager? { get }
|
||||
|
||||
var activeAccountContexts: Signal<(primary: AccountContext?, accounts: [(AccountRecordId, AccountContext, Int32)], currentAuth: UnauthorizedAccount?), NoError> { get }
|
||||
var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get }
|
||||
|
@ -7,6 +7,7 @@ load(
|
||||
"telegram_is_appstore_build",
|
||||
"telegram_appstore_id",
|
||||
"telegram_app_specific_url_scheme",
|
||||
"telegram_premium_iap_product_id",
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@ -25,6 +26,7 @@ objc_library(
|
||||
"-DAPP_CONFIG_IS_APPSTORE_BUILD={}".format(telegram_is_appstore_build),
|
||||
"-DAPP_CONFIG_APPSTORE_ID={}".format(telegram_appstore_id),
|
||||
"-DAPP_SPECIFIC_URL_SCHEME=\\\"{}\\\"".format(telegram_app_specific_url_scheme),
|
||||
"-DAPP_CONFIG_PREMIUM_IAP_PRODUCT_ID=\\\"{}\\\"".format(telegram_premium_iap_product_id),
|
||||
],
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
|
@ -18,6 +18,7 @@
|
||||
@property (nonatomic, readonly) bool isAppStoreBuild;
|
||||
@property (nonatomic, readonly) int64_t appStoreId;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nonnull appSpecificUrlScheme;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nonnull premiumIAPProductId;
|
||||
|
||||
+ (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId;
|
||||
- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict;
|
||||
|
@ -185,6 +185,10 @@ API_AVAILABLE(ios(10))
|
||||
return @(APP_SPECIFIC_URL_SCHEME);
|
||||
}
|
||||
|
||||
- (NSString *)premiumIAPProductId {
|
||||
return @(APP_CONFIG_PREMIUM_IAP_PRODUCT_ID);
|
||||
}
|
||||
|
||||
+ (NSString * _Nullable)bundleSeedId {
|
||||
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
|
||||
|
20
submodules/InAppPurchaseManager/BUILD
Normal file
20
submodules/InAppPurchaseManager/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "InAppPurchaseManager",
|
||||
module_name = "InAppPurchaseManager",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
22
submodules/InAppPurchaseManager/Info.plist
Normal file
22
submodules/InAppPurchaseManager/Info.plist
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,143 @@
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
import SwiftSignalKit
|
||||
import StoreKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private final class PaymentTransactionContext {
|
||||
var state: SKPaymentTransactionState?
|
||||
let subscriber: (SKPaymentTransactionState) -> Void
|
||||
|
||||
init(subscriber: @escaping (SKPaymentTransactionState) -> Void) {
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
}
|
||||
|
||||
public final class InAppPurchaseManager: NSObject {
|
||||
public final class Product {
|
||||
let skProduct: SKProduct
|
||||
|
||||
init(skProduct: SKProduct) {
|
||||
self.skProduct = skProduct
|
||||
}
|
||||
|
||||
public var price: String {
|
||||
let numberFormatter = NumberFormatter()
|
||||
numberFormatter.numberStyle = .currency
|
||||
numberFormatter.locale = self.skProduct.priceLocale
|
||||
return numberFormatter.string(from: self.skProduct.price) ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
public enum PurchaseResult {
|
||||
case success
|
||||
}
|
||||
|
||||
public enum PurchaseError {
|
||||
case generic
|
||||
}
|
||||
|
||||
private let premiumProductId: String
|
||||
|
||||
private var products: [Product] = []
|
||||
private var productsPromise = Promise<[Product]>()
|
||||
private var productRequest: SKProductsRequest?
|
||||
|
||||
private let stateQueue = Queue()
|
||||
private var paymentContexts: [String: PaymentTransactionContext] = [:]
|
||||
|
||||
public init(premiumProductId: String) {
|
||||
self.premiumProductId = premiumProductId
|
||||
|
||||
super.init()
|
||||
|
||||
SKPaymentQueue.default().add(self)
|
||||
self.requestProducts()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
}
|
||||
|
||||
private func requestProducts() {
|
||||
guard !self.premiumProductId.isEmpty else {
|
||||
return
|
||||
}
|
||||
let productRequest = SKProductsRequest(productIdentifiers: Set([self.premiumProductId]))
|
||||
productRequest.delegate = self
|
||||
productRequest.start()
|
||||
|
||||
self.productRequest = productRequest
|
||||
}
|
||||
|
||||
|
||||
public var availableProducts: Signal<[Product], NoError> {
|
||||
if self.products.isEmpty && self.productRequest == nil {
|
||||
self.requestProducts()
|
||||
}
|
||||
return self.productsPromise.get()
|
||||
}
|
||||
|
||||
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseResult, PurchaseError> {
|
||||
let payment = SKMutablePayment(product: product.skProduct)
|
||||
payment.applicationUsername = "\(account.peerId.id._internalGetInt64Value())"
|
||||
SKPaymentQueue.default().add(payment)
|
||||
|
||||
let productIdentifier = payment.productIdentifier
|
||||
let signal = Signal<PurchaseResult, PurchaseError> { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.stateQueue.async {
|
||||
let paymentContext = PaymentTransactionContext(subscriber: { state in
|
||||
switch state {
|
||||
case .purchased, .restored:
|
||||
subscriber.putNext(.success)
|
||||
subscriber.putCompletion()
|
||||
case .failed:
|
||||
subscriber.putError(.generic)
|
||||
case .deferred, .purchasing:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
self.paymentContexts[productIdentifier] = paymentContext
|
||||
|
||||
disposable.set(ActionDisposable { [weak paymentContext] in
|
||||
self.stateQueue.async {
|
||||
if let current = self.paymentContexts[productIdentifier], current === paymentContext {
|
||||
self.paymentContexts.removeValue(forKey: productIdentifier)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
return signal
|
||||
}
|
||||
}
|
||||
|
||||
extension InAppPurchaseManager: SKProductsRequestDelegate {
|
||||
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
self.productRequest = nil
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.productsPromise.set(.single(response.products.map { Product(skProduct: $0) }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
||||
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
if let transaction = transactions.first {
|
||||
let productIdentifier = transaction.payment.productIdentifier
|
||||
self.stateQueue.async {
|
||||
if let context = self.paymentContexts[productIdentifier] {
|
||||
context.subscriber(transaction.transactionState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@ swift_library(
|
||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
|
||||
"//submodules/Components/Forms/PrefixSectionGroupComponent:PrefixSectionGroupComponent",
|
||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||
"//submodules/ConfettiEffect:ConfettiEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,8 @@ import BundleIconComponent
|
||||
import SolidRoundedButtonComponent
|
||||
import Markdown
|
||||
import SceneKit
|
||||
import InAppPurchaseManager
|
||||
import ConfettiEffect
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
@ -750,7 +752,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
let strings = environment.strings
|
||||
|
||||
let availableWidth = context.availableSize.width
|
||||
let sideInsets = sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right
|
||||
let sideInsets = sideInset * 2.0 + environment.safeInsets.left + environment.safeInsets.right
|
||||
var size = CGSize(width: context.availableSize.width, height: 0.0)
|
||||
|
||||
let overscroll = overscroll.update(
|
||||
@ -1060,11 +1062,15 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let updateInProgress: (Bool) -> Void
|
||||
let completion: () -> Void
|
||||
|
||||
init(context: AccountContext) {
|
||||
init(context: AccountContext, updateInProgress: @escaping (Bool) -> Void, completion: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateInProgress = updateInProgress
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
|
||||
static func ==(lhs: PremiumIntroScreenComponent, rhs: PremiumIntroScreenComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
@ -1073,12 +1079,68 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let updateInProgress: (Bool) -> Void
|
||||
private let completion: () -> Void
|
||||
|
||||
var topContentOffset: CGFloat?
|
||||
var bottomContentOffset: CGFloat?
|
||||
|
||||
var inProgress = false
|
||||
var premiumProduct: InAppPurchaseManager.Product?
|
||||
private var disposable: Disposable?
|
||||
private var actionDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, updateInProgress: @escaping (Bool) -> Void, completion: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateInProgress = updateInProgress
|
||||
self.completion = completion
|
||||
|
||||
super.init()
|
||||
|
||||
if let inAppPurchaseManager = context.sharedContext.inAppPurchaseManager {
|
||||
self.disposable = (inAppPurchaseManager.availableProducts
|
||||
|> deliverOnMainQueue).start(next: { [weak self] products in
|
||||
if let strongSelf = self {
|
||||
strongSelf.premiumProduct = products.first
|
||||
strongSelf.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.actionDisposable.dispose()
|
||||
}
|
||||
|
||||
func buy() {
|
||||
guard let inAppPurchaseManager = self.context.sharedContext.inAppPurchaseManager,
|
||||
let premiumProduct = self.premiumProduct, !self.inProgress else {
|
||||
return
|
||||
}
|
||||
|
||||
self.inProgress = true
|
||||
self.updateInProgress(true)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
self.actionDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: self.context.account)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion()
|
||||
}
|
||||
}, error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.inProgress = false
|
||||
strongSelf.updateInProgress(false)
|
||||
strongSelf.updated(transition: .immediate)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State()
|
||||
return State(context: self.context, updateInProgress: self.updateInProgress, completion: self.completion)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
@ -1138,7 +1200,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: environment.strings.Premium_SubscribeFor("$5").string,
|
||||
title: environment.strings.Premium_SubscribeFor(state.premiumProduct?.price ?? "—").string,
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: .black,
|
||||
backgroundColors: [
|
||||
@ -1152,7 +1214,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: true,
|
||||
action: {}
|
||||
action: {
|
||||
state.buy()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0),
|
||||
transition: context.transition)
|
||||
@ -1278,8 +1342,18 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: PremiumIntroScreenComponent(context: context), navigationBarAppearance: .transparent)
|
||||
|
||||
var updateInProgressImpl: ((Bool) -> Void)?
|
||||
var completionImpl: (() -> Void)?
|
||||
super.init(context: context, component: PremiumIntroScreenComponent(
|
||||
context: context,
|
||||
updateInProgress: { inProgress in
|
||||
updateInProgressImpl?(inProgress)
|
||||
},
|
||||
completion: {
|
||||
completionImpl?()
|
||||
}
|
||||
), navigationBarAppearance: .transparent)
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -1287,6 +1361,23 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
self.navigationItem.setLeftBarButton(cancelItem, animated: false)
|
||||
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
updateInProgressImpl = { [weak self] inProgress in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationItem.leftBarButtonItem?.isEnabled = !inProgress
|
||||
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = inProgress
|
||||
strongSelf.view.disablesInteractiveModalDismiss = inProgress
|
||||
}
|
||||
}
|
||||
|
||||
completionImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
|
||||
Queue.mainQueue().after(2.0, {
|
||||
self?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -186,15 +186,14 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight))
|
||||
self.container.frame = containerFrame
|
||||
|
||||
self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight))
|
||||
self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0 + 1.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight))
|
||||
self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0, height: lineHeight))
|
||||
self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0, height: lineHeight))
|
||||
|
||||
self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight))
|
||||
if self.activeBackground.animation(forKey: "movement") == nil {
|
||||
self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0, y: lineHeight / 2.0)
|
||||
self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0 - self.activeBackground.frame.width * 0.35, y: lineHeight / 2.0)
|
||||
}
|
||||
|
||||
|
||||
let countWidth: CGFloat
|
||||
if let badgeText = component.badgeText {
|
||||
switch badgeText.count {
|
||||
@ -219,10 +218,10 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
|
||||
self.badgeView.bounds = CGRect(origin: .zero, size: badgeSize)
|
||||
self.badgeView.center = CGPoint(x: availableSize.width / 2.0, y: 82.0)
|
||||
if self.badgeForeground.animation(forKey: "movement") == nil {
|
||||
self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0, y: badgeSize.height / 2.0)
|
||||
}
|
||||
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height))
|
||||
if self.badgeForeground.animation(forKey: "movement") == nil {
|
||||
self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeSize.height / 2.0)
|
||||
}
|
||||
|
||||
self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0)
|
||||
self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0)
|
||||
|
@ -309,12 +309,13 @@ private func generatePremiumReactionIcon() -> UIImage? {
|
||||
}
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.85, 1.0]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
@ -792,10 +792,10 @@ public final class SolidRoundedButtonView: UIView {
|
||||
}
|
||||
|
||||
if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView {
|
||||
if buttonBackgroundAnimationView.layer.animation(forKey: "movement") == nil {
|
||||
buttonBackgroundAnimationView.center = CGPoint(x: buttonSize.width * 2.4 / 2.0, y: buttonSize.height / 2.0)
|
||||
}
|
||||
buttonBackgroundAnimationView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height))
|
||||
if buttonBackgroundAnimationView.layer.animation(forKey: "movement") == nil {
|
||||
buttonBackgroundAnimationView.center = CGPoint(x: buttonSize.width * 2.4 / 2.0 - buttonBackgroundAnimationView.frame.width * 0.35, y: buttonSize.height / 2.0)
|
||||
}
|
||||
self.setupGradientAnimations()
|
||||
}
|
||||
|
||||
|
@ -414,7 +414,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
|
||||
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
|
||||
dict[1345295095] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) }
|
||||
dict[1080663248] = { return Api.MessageAction.parse_messageActionPaymentSent($0) }
|
||||
dict[-1776926890] = { return Api.MessageAction.parse_messageActionPaymentSent($0) }
|
||||
dict[-1892568281] = { return Api.MessageAction.parse_messageActionPaymentSentMe($0) }
|
||||
dict[-2132731265] = { return Api.MessageAction.parse_messageActionPhoneCall($0) }
|
||||
dict[-1799538451] = { return Api.MessageAction.parse_messageActionPinMessage($0) }
|
||||
@ -985,6 +985,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) }
|
||||
dict[816245886] = { return Api.messages.Stickers.parse_stickers($0) }
|
||||
dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) }
|
||||
dict[-1442723025] = { return Api.messages.TranscribedAudio.parse_transcribedAudio($0) }
|
||||
dict[1741309751] = { return Api.messages.TranslatedText.parse_translateNoResult($0) }
|
||||
dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
@ -1739,6 +1740,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.Stickers:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.TranscribedAudio:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.TranslatedText:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.VotesList:
|
||||
|
@ -1009,7 +1009,7 @@ public extension Api {
|
||||
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
|
||||
case messageActionHistoryClear
|
||||
case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64])
|
||||
case messageActionPaymentSent(currency: String, totalAmount: Int64)
|
||||
case messageActionPaymentSent(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?)
|
||||
case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge)
|
||||
case messageActionPhoneCall(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?)
|
||||
case messageActionPinMessage
|
||||
@ -1170,12 +1170,14 @@ public extension Api {
|
||||
serializeInt64(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .messageActionPaymentSent(let currency, let totalAmount):
|
||||
case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug):
|
||||
if boxed {
|
||||
buffer.appendInt32(1080663248)
|
||||
buffer.appendInt32(-1776926890)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
serializeInt64(totalAmount, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(invoiceSlug!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge):
|
||||
if boxed {
|
||||
@ -1303,8 +1305,8 @@ public extension Api {
|
||||
return ("messageActionHistoryClear", [])
|
||||
case .messageActionInviteToGroupCall(let call, let users):
|
||||
return ("messageActionInviteToGroupCall", [("call", String(describing: call)), ("users", String(describing: users))])
|
||||
case .messageActionPaymentSent(let currency, let totalAmount):
|
||||
return ("messageActionPaymentSent", [("currency", String(describing: currency)), ("totalAmount", String(describing: totalAmount))])
|
||||
case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug):
|
||||
return ("messageActionPaymentSent", [("flags", String(describing: flags)), ("currency", String(describing: currency)), ("totalAmount", String(describing: totalAmount)), ("invoiceSlug", String(describing: invoiceSlug))])
|
||||
case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge):
|
||||
return ("messageActionPaymentSentMe", [("flags", String(describing: flags)), ("currency", String(describing: currency)), ("totalAmount", String(describing: totalAmount)), ("payload", String(describing: payload)), ("info", String(describing: info)), ("shippingOptionId", String(describing: shippingOptionId)), ("charge", String(describing: charge))])
|
||||
case .messageActionPhoneCall(let flags, let callId, let reason, let duration):
|
||||
@ -1565,14 +1567,20 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.MessageAction.messageActionPaymentSent(currency: _1!, totalAmount: _2!)
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MessageAction.messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -488,6 +488,42 @@ public extension Api.messages {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum TranscribedAudio: TypeConstructorDescription {
|
||||
case transcribedAudio(text: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .transcribedAudio(let text):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1442723025)
|
||||
}
|
||||
serializeString(text, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .transcribedAudio(let text):
|
||||
return ("transcribedAudio", [("text", String(describing: text))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.messages.TranscribedAudio.transcribedAudio(text: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.messages {
|
||||
enum TranslatedText: TypeConstructorDescription {
|
||||
case translateNoResult
|
||||
@ -1464,95 +1500,3 @@ public extension Api.photos {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.photos {
|
||||
enum Photos: TypeConstructorDescription {
|
||||
case photos(photos: [Api.Photo], users: [Api.User])
|
||||
case photosSlice(count: Int32, photos: [Api.Photo], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photos(let photos, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1916114267)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(photos.count))
|
||||
for item in photos {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .photosSlice(let count, let photos, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(352657236)
|
||||
}
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(photos.count))
|
||||
for item in photos {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photos(let photos, let users):
|
||||
return ("photos", [("photos", String(describing: photos)), ("users", String(describing: users))])
|
||||
case .photosSlice(let count, let photos, let users):
|
||||
return ("photosSlice", [("count", String(describing: count)), ("photos", String(describing: photos)), ("users", String(describing: users))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_photos(_ reader: BufferReader) -> Photos? {
|
||||
var _1: [Api.Photo]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self)
|
||||
}
|
||||
var _2: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.photos.Photos.photos(photos: _1!, users: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_photosSlice(_ reader: BufferReader) -> Photos? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Api.Photo]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self)
|
||||
}
|
||||
var _3: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,95 @@
|
||||
public extension Api.photos {
|
||||
enum Photos: TypeConstructorDescription {
|
||||
case photos(photos: [Api.Photo], users: [Api.User])
|
||||
case photosSlice(count: Int32, photos: [Api.Photo], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photos(let photos, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1916114267)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(photos.count))
|
||||
for item in photos {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .photosSlice(let count, let photos, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(352657236)
|
||||
}
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(photos.count))
|
||||
for item in photos {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photos(let photos, let users):
|
||||
return ("photos", [("photos", String(describing: photos)), ("users", String(describing: users))])
|
||||
case .photosSlice(let count, let photos, let users):
|
||||
return ("photosSlice", [("count", String(describing: count)), ("photos", String(describing: photos)), ("users", String(describing: users))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_photos(_ reader: BufferReader) -> Photos? {
|
||||
var _1: [Api.Photo]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self)
|
||||
}
|
||||
var _2: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.photos.Photos.photos(photos: _1!, users: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_photosSlice(_ reader: BufferReader) -> Photos? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Api.Photo]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self)
|
||||
}
|
||||
var _3: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, recentMessageInteractions: [Api.MessageInteractionCounters])
|
||||
|
@ -6024,6 +6024,22 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.TranscribedAudio>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(647928393)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.transcribeAudio", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranscribedAudio? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.TranscribedAudio?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.TranscribedAudio
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func translateText(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, text: String?, fromLang: String?, toLang: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.TranslatedText>) {
|
||||
let buffer = Buffer()
|
||||
@ -6177,6 +6193,21 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func assignAppStoreTransaction(transactionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1654235439)
|
||||
serializeString(transactionId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("transactionId", String(describing: transactionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
@ -7222,11 +7253,11 @@ public extension Api.functions.upload {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.upload {
|
||||
static func getFileHashes(location: Api.InputFileLocation, offset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) {
|
||||
static func getFileHashes(location: Api.InputFileLocation, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-956147407)
|
||||
buffer.appendInt32(-1856595926)
|
||||
location.serialize(buffer, true)
|
||||
serializeInt32(offset, buffer: buffer, boxed: false)
|
||||
serializeInt64(offset, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "upload.getFileHashes", parameters: [("location", String(describing: location)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: [Api.FileHash]?
|
||||
|
@ -40,7 +40,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo))
|
||||
case .messageActionEmpty:
|
||||
return nil
|
||||
case let .messageActionPaymentSent(currency, totalAmount):
|
||||
case let .messageActionPaymentSent(_, currency, totalAmount, _):
|
||||
return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount))
|
||||
case .messageActionPaymentSentMe:
|
||||
return nil
|
||||
|
@ -456,6 +456,17 @@ extension Api.Updates {
|
||||
}
|
||||
|
||||
extension Api.Updates {
|
||||
var users: [Api.User] {
|
||||
switch self {
|
||||
case let .updates(_, users, _, _, _):
|
||||
return users
|
||||
case let .updatesCombined(_, users, _, _, _, _):
|
||||
return users
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
var messages: [Api.Message] {
|
||||
switch self {
|
||||
case let .updates(updates, _, _, _, _):
|
||||
|
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import MtProtoKit
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
|
||||
public enum AssignAppStoreTransactionError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_assignAppStoreTransaction(account: Account, transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
|
||||
return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId))
|
||||
|> mapError { _ -> AssignAppStoreTransactionError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
|
||||
return .never()
|
||||
}
|
||||
}
|
@ -36,5 +36,9 @@ public extension TelegramEngine {
|
||||
public func clearBotPaymentInfo(info: BotPaymentInfo) -> Signal<Void, NoError> {
|
||||
return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
|
||||
}
|
||||
|
||||
public func assignAppStoreTransaction(transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
|
||||
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,12 +304,13 @@ public struct PresentationResourcesChat {
|
||||
}
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
@ -329,12 +330,13 @@ public struct PresentationResourcesChat {
|
||||
}
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.85, 1.0]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
@ -238,12 +238,13 @@ public struct PresentationResourcesChatList {
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
@ -50,6 +50,33 @@ public struct PresentationResourcesSettings {
|
||||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
|
||||
context.addPath(path.cgPath)
|
||||
context.clip()
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0x6b93ff).cgColor,
|
||||
UIColor(rgb: 0x6b93ff).cgColor,
|
||||
UIColor(rgb: 0x8d77ff).cgColor,
|
||||
UIColor(rgb: 0xb56eec).cgColor,
|
||||
UIColor(rgb: 0xb56eec).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let passport = renderIcon(name: "Settings/Menu/Passport")
|
||||
public static let watch = renderIcon(name: "Settings/Menu/Watch")
|
||||
|
@ -270,6 +270,7 @@ swift_library(
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
@ -34,6 +34,7 @@ import TelegramAudio
|
||||
import DebugSettingsUI
|
||||
import BackgroundTasks
|
||||
import UIKitRuntimeUtils
|
||||
import InAppPurchaseManager
|
||||
|
||||
#if canImport(AppCenter)
|
||||
import AppCenter
|
||||
@ -705,6 +706,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
UINavigationController.attemptRotationToDeviceOrientation()
|
||||
})
|
||||
|
||||
let inAppPurchaseManager = InAppPurchaseManager(premiumProductId: buildConfig.premiumIAPProductId)
|
||||
|
||||
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true)
|
||||
self.accountManager = accountManager
|
||||
@ -748,7 +751,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
})
|
||||
|
||||
var setPresentationCall: ((PresentationCall?) -> Void)?
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), setNotificationCall: { call in
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, inAppPurchaseManager: inAppPurchaseManager, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), setNotificationCall: { call in
|
||||
setPresentationCall?(call)
|
||||
}, navigateToChat: { accountId, peerId, messageId in
|
||||
self.openChatWhenReady(accountId: accountId, peerId: peerId, messageId: messageId)
|
||||
|
@ -136,7 +136,7 @@ public final class NotificationViewControllerImpl {
|
||||
return nil
|
||||
})
|
||||
|
||||
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), resolvedDeviceName: nil), rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
|
||||
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), resolvedDeviceName: nil), inAppPurchaseManager: nil, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
|
||||
|
||||
presentationDataPromise.set(sharedAccountContext!.presentationData)
|
||||
}
|
||||
|
@ -2306,12 +2306,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xa34ecf).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor,
|
||||
UIColor(rgb: 0xff7923).cgColor
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
@ -67,6 +67,7 @@ import TranslateUI
|
||||
import ChatPresentationInterfaceState
|
||||
import CreateExternalMediaStreamScreen
|
||||
import PaymentMethodUI
|
||||
import PremiumUI
|
||||
|
||||
protocol PeerInfoScreenItem: AnyObject {
|
||||
var id: AnyHashable { get }
|
||||
@ -420,6 +421,7 @@ private enum PeerInfoSettingsSection {
|
||||
case appearance
|
||||
case language
|
||||
case stickers
|
||||
case premium
|
||||
case passport
|
||||
case watch
|
||||
case support
|
||||
@ -722,6 +724,10 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
||||
interaction.openSettings(.language)
|
||||
}))
|
||||
|
||||
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Telegram Premium", icon: PresentationResourcesSettings.premium, action: {
|
||||
interaction.openSettings(.premium)
|
||||
}))
|
||||
|
||||
/*items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Payment Method", icon: PresentationResourcesSettings.language, action: {
|
||||
interaction.openPaymentMethod()
|
||||
}))*/
|
||||
@ -6176,6 +6182,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
self.controller?.push(themeSettingsController(context: self.context))
|
||||
case .language:
|
||||
self.controller?.push(LocalizationListController(context: self.context))
|
||||
case .premium:
|
||||
self.controller?.push(PremiumIntroScreen(context: self.context))
|
||||
case .stickers:
|
||||
if let settings = self.data?.globalSettings {
|
||||
self.controller?.push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in
|
||||
|
@ -232,7 +232,7 @@ public class ShareRootControllerImpl {
|
||||
return nil
|
||||
})
|
||||
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), resolvedDeviceName: nil), rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), resolvedDeviceName: nil), inAppPurchaseManager: nil, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
|
||||
presentationDataPromise.set(sharedContext.presentationData)
|
||||
internalContext = InternalContext(sharedContext: sharedContext)
|
||||
globalInternalContext = internalContext
|
||||
|
@ -24,6 +24,7 @@ import PresentationDataUtils
|
||||
import LocationUI
|
||||
import AppLock
|
||||
import WallpaperBackgroundNode
|
||||
import InAppPurchaseManager
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -88,6 +89,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public let contactDataManager: DeviceContactDataManager?
|
||||
public let locationManager: DeviceLocationManager?
|
||||
public var callManager: PresentationCallManager?
|
||||
public var inAppPurchaseManager: InAppPurchaseManager?
|
||||
|
||||
private var callDisposable: Disposable?
|
||||
private var callStateDisposable: Disposable?
|
||||
@ -161,7 +163,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
private var spotlightDataContext: SpotlightDataContext?
|
||||
private var widgetDataContext: WidgetDataContext?
|
||||
|
||||
public init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) {
|
||||
public init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, inAppPurchaseManager: InAppPurchaseManager?, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
precondition(!testHasInstance)
|
||||
@ -175,6 +177,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
self.navigateToChatImpl = navigateToChat
|
||||
self.displayUpgradeProgress = displayUpgradeProgress
|
||||
self.appLockContext = appLockContext
|
||||
self.inAppPurchaseManager = inAppPurchaseManager
|
||||
|
||||
self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
return fetchCachedSharedResourceRepresentation(accountManager: accountManager, resource: resource, representation: representation)
|
||||
|
@ -130,12 +130,6 @@ public final class TelegramRootController: NavigationController {
|
||||
self.accountSettingsController = accountSettingsController
|
||||
self.rootTabController = tabBarController
|
||||
self.pushViewController(tabBarController, animated: false)
|
||||
|
||||
// Queue.mainQueue().after(1.0) {
|
||||
//// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {})
|
||||
// let screen = PremiumIntroScreen(context: self.context, action: {})
|
||||
// self.chatListController?.push(screen)
|
||||
// }
|
||||
}
|
||||
|
||||
public func updateRootControllers(showCallsTab: Bool) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user