diff --git a/build-system/example-configuration/variables.bzl b/build-system/example-configuration/variables.bzl index 5403ae8176..26b6550f2e 100644 --- a/build-system/example-configuration/variables.bzl +++ b/build-system/example-configuration/variables.bzl @@ -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 diff --git a/build-system/prepare-build-variables-Telegram.sh b/build-system/prepare-build-variables-Telegram.sh index ca3d9a9542..62862a6ddd 100755 --- a/build-system/prepare-build-variables-Telegram.sh +++ b/build-system/prepare-build-variables-Telegram.sh @@ -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 diff --git a/build-system/verify.sh b/build-system/verify.sh index 54fd0a9d30..fce7a6850c 100644 --- a/build-system/verify.sh +++ b/build-system/verify.sh @@ -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" diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index ccac7c6b1b..b9c7a36100 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/Utils/RangeSet:RangeSet", + "//submodules/InAppPurchaseManager:InAppPurchaseManager", ], visibility = [ "//visibility:public", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index eac8787137..a56654c9fe 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -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 } diff --git a/submodules/BuildConfig/BUILD b/submodules/BuildConfig/BUILD index 10c5c92dbb..83ee53988e 100644 --- a/submodules/BuildConfig/BUILD +++ b/submodules/BuildConfig/BUILD @@ -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", diff --git a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h index d735dd74bc..a433870b93 100644 --- a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h +++ b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.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; diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index 3dde1749ae..a589f33372 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -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, diff --git a/submodules/InAppPurchaseManager/BUILD b/submodules/InAppPurchaseManager/BUILD new file mode 100644 index 0000000000..ce210e161f --- /dev/null +++ b/submodules/InAppPurchaseManager/BUILD @@ -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", + ], +) diff --git a/submodules/InAppPurchaseManager/Info.plist b/submodules/InAppPurchaseManager/Info.plist new file mode 100644 index 0000000000..e1fe4cfb7b --- /dev/null +++ b/submodules/InAppPurchaseManager/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift new file mode 100644 index 0000000000..5e5f5d7339 --- /dev/null +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -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 { + let payment = SKMutablePayment(product: product.skProduct) + payment.applicationUsername = "\(account.peerId.id._internalGetInt64Value())" + SKPaymentQueue.default().add(payment) + + let productIdentifier = payment.productIdentifier + let signal = Signal { 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) + } + } + } + } +} diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 6a0a5e17a1..3fe8dd5760 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -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", diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index b326b49c5c..9617ed59a1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -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) { diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index a50009a06d..d90f3fff50 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -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) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 623b5b8085..e57be1642b 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -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()) diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 895699be86..25278fd18e 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -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() } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 4945e00a2e..7b282e7966 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 0a7fe0a125..e99bde0f5d 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 0635fa8ff7..2acb37d32a 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -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 - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index ad5ef9bca8..58fa0773fb 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -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]) diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 09fd916287..fff5642c9c 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -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) { + 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) { 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) { + 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) { 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]? diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index dd34b5c563..ea07ece437 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 416aa4d2c0..9b84511abb 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -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, _, _, _, _): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift new file mode 100644 index 0000000000..390fc57949 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift @@ -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 { + return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId)) + |> mapError { _ -> AssignAppStoreTransactionError in + return .generic + } + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + + return .never() + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index c953688813..73720b5c89 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -36,5 +36,9 @@ public extension TelegramEngine { public func clearBotPaymentInfo(info: BotPaymentInfo) -> Signal { return _internal_clearBotPaymentInfo(network: self.account.network, info: info) } + + public func assignAppStoreTransaction(transactionId: String) -> Signal { + return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId) + } } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 0bbb3e2752..58778dfbb2 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -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()) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index b917061bee..fdd197f03b 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -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()) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index e17423dfc6..3b8b55c925 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -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") diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index a50599ac01..064119386a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -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, diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 4beacfcc2b..c38dce315d 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -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(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true) self.accountManager = accountManager @@ -748,7 +751,7 @@ private func extractAccountManagerState(records: AccountRecordsView 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) diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index cd7d5d198f..b8463210e1 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 0e2952325b..a108ce37c8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -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()) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a612fe6fa5..b86363f91d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 2b9d4a1521..92b1b84c23 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 468dc26f76..9350011751 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, 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, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, inAppPurchaseManager: InAppPurchaseManager?, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, 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 in return fetchCachedSharedResourceRepresentation(accountManager: accountManager, resource: resource, representation: representation) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 19ec98a606..18e0cf5643 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -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) {