diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 1d1fa95e8b..05d73cdc05 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -32,6 +32,11 @@ public final class InAppPurchaseManager: NSObject { case notAllowed } + public enum RestoreState { + case succeed + case failed + } + private final class PaymentTransactionContext { var state: SKPaymentTransactionState? let subscriber: (TransactionState) -> Void @@ -59,6 +64,8 @@ public final class InAppPurchaseManager: NSObject { private let stateQueue = Queue() private var paymentContexts: [String: PaymentTransactionContext] = [:] + private var onRestoreCompletion: ((RestoreState) -> Void)? + private let disposableSet = DisposableDict() public init(engine: TelegramEngine, premiumProductId: String) { @@ -79,6 +86,7 @@ public final class InAppPurchaseManager: NSObject { guard !self.premiumProductId.isEmpty else { return } + Logger.shared.log("InAppPurchaseManager", "Requesting products") let productRequest = SKProductsRequest(productIdentifiers: Set([self.premiumProductId])) productRequest.delegate = self productRequest.start() @@ -93,7 +101,17 @@ public final class InAppPurchaseManager: NSObject { return self.productsPromise.get() } + public func restorePurchases(completion: @escaping (RestoreState) -> Void) { + Logger.shared.log("InAppPurchaseManager", "Restoring purchases") + self.onRestoreCompletion = completion + + let paymentQueue = SKPaymentQueue.default() + paymentQueue.restoreCompletedTransactions() + } + public func finishAllTransactions() { + Logger.shared.log("InAppPurchaseManager", "Finishing all transactions") + let paymentQueue = SKPaymentQueue.default() let transactions = paymentQueue.transactions for transaction in transactions { @@ -102,6 +120,8 @@ public final class InAppPurchaseManager: NSObject { } public func buyProduct(_ product: Product, account: Account) -> Signal { + Logger.shared.log("InAppPurchaseManager", "Buying product: \(product.skProduct.productIdentifier), price \(product.price)") + let payment = SKPayment(product: product.skProduct) SKPaymentQueue.default().add(payment) @@ -162,7 +182,10 @@ extension InAppPurchaseManager: SKProductsRequestDelegate { self.productRequest = nil Queue.mainQueue().async { - self.productsPromise.set(.single(response.products.map { Product(skProduct: $0) })) + let products = response.products.map { Product(skProduct: $0) } + + Logger.shared.log("InAppPurchaseManager", "Received products \(products.map({ $0.skProduct.productIdentifier }).joined(separator: ", "))") + self.productsPromise.set(.single(products)) } } } @@ -187,37 +210,46 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { let transactionState: TransactionState? switch transaction.transactionState { case .purchased: + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased") let transactionIdentifier = transaction.transactionIdentifier transactionState = .purchased(transactionId: transactionIdentifier) if let transactionIdentifier = transactionIdentifier { self.disposableSet.set( self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") failed to assign AppStore transaction") queue.finishTransaction(transaction) }, completed: { + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") successfully assigned AppStore transaction") queue.finishTransaction(transaction) }), forKey: transactionIdentifier ) } case .restored: + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "") restroring") let transactionIdentifier = transaction.transactionIdentifier transactionState = .restored(transactionId: transactionIdentifier) if let transactionIdentifier = transactionIdentifier { self.disposableSet.set( self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: true).start(error: { _ in + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") failed to assign AppStore transaction") queue.finishTransaction(transaction) }, completed: { + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") successfully assigned AppStore transaction") queue.finishTransaction(transaction) }), forKey: transactionIdentifier ) } case .failed: + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") failed \((transaction.error as? SKError)?.localizedDescription ?? "")") transactionState = .failed(error: transaction.error as? SKError) queue.finishTransaction(transaction) case .purchasing: + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") purchasing") transactionState = .purchasing case .deferred: + Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") deferred") transactionState = .deferred default: transactionState = nil @@ -230,4 +262,20 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } } } + + public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { + if let onRestoreCompletion = self.onRestoreCompletion { + Logger.shared.log("InAppPurchaseManager", "Transactions restoration finished") + onRestoreCompletion(.succeed) + self.onRestoreCompletion = nil + } + } + + public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { + if let onRestoreCompletion = self.onRestoreCompletion { + Logger.shared.log("InAppPurchaseManager", "Transactions restoration failed with error \((error as? SKError)?.localizedDescription ?? "")") + onRestoreCompletion(.failed) + self.onRestoreCompletion = nil + } + } } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 2847ebd907..a4321c24a8 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -382,6 +382,8 @@ private final class SectionGroupComponent: Component { buttonView = current } else { buttonView = HighlightTrackingButton() + buttonView.isMultipleTouchEnabled = false + buttonView.isExclusiveTouch = true buttonView.addTarget(self, action: #selector(self.buttonPressed(_:)), for: .touchUpInside) self.buttonViews[item.content.id] = buttonView self.addSubview(buttonView) diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 169aaa88d2..ae976a8581 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -751,6 +751,8 @@ public final class SolidRoundedButtonView: UIView { } self.buttonNode = HighlightTrackingButton() + self.buttonNode.isMultipleTouchEnabled = false + self.buttonNode.isExclusiveTouch = true self.titleNode = ImmediateTextView() self.titleNode.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 9c84aa6a0f..0feb087087 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -156,7 +156,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - titleRightIcon = .mute + if titleCredibilityIcon != .verified { + titleRightIcon = .mute + } } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index d2e783a020..7a38fd0b6f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2609,7 +2609,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { let _ = nextPanelSubtitleNodeLayout[TitleNodeStateRegular]!.size let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size + var titleHorizontalOffset: CGFloat = 0.0 if let image = self.titleCredibilityIconNode.image { + titleHorizontalOffset = -(image.size.width + 4.0) / 2.0 transition.updateFrame(node: self.titleCredibilityIconNode, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0, y: floor((titleSize.height - image.size.height) / 2.0) + 1.0), size: image.size)) transition.updateFrame(node: self.titleExpandedCredibilityIconNode, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - image.size.height) / 2.0) + 1.0), size: image.size)) } @@ -2629,14 +2631,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: minTitleFrame.maxY + 2.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: width - usernameSize.width - 16.0, y: minTitleFrame.midY - usernameSize.height / 2.0), size: usernameSize) } else { - titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 7.0 + (subtitleSize.height.isZero ? 11.0 : 0.0) + 11.0), size: titleSize) + titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 7.0 + (subtitleSize.height.isZero ? 11.0 : 0.0) + 11.0), size: titleSize) let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width if usernameSize.width == 0.0 { - subtitleFrame = CGRect(origin: CGPoint(x: floor((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) - usernameFrame = CGRect(origin: CGPoint(x: floor((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + usernameFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) } else { - subtitleFrame = CGRect(origin: CGPoint(x: floor((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize) } } @@ -2851,10 +2853,15 @@ final class PeerInfoHeaderNode: ASDisplayNode { neutralTitleScale = 0.7 neutralSubtitleScale = 1.0 } - + let titleScale = (transitionFraction * transitionSourceTitleFrame.height + (1.0 - transitionFraction) * titleFrame.height * neutralTitleScale) / (titleFrame.height) let subtitleScale = max(0.01, min(10.0, (transitionFraction * transitionSourceSubtitleFrame.height + (1.0 - transitionFraction) * subtitleFrame.height * neutralSubtitleScale) / (subtitleFrame.height))) + var titleFrame = titleFrame + if !self.isAvatarExpanded { + titleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? 0.0 : titleHorizontalOffset * titleScale, dy: 0.0) + } + let titleCenter = CGPoint(x: transitionFraction * transitionSourceTitleFrame.midX + (1.0 - transitionFraction) * titleFrame.midX, y: transitionFraction * transitionSourceTitleFrame.midY + (1.0 - transitionFraction) * titleFrame.midY) let subtitleCenter = CGPoint(x: transitionFraction * transitionSourceSubtitleFrame.midX + (1.0 - transitionFraction) * subtitleFrame.midX, y: transitionFraction * transitionSourceSubtitleFrame.midY + (1.0 - transitionFraction) * subtitleFrame.midY) @@ -2885,7 +2892,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { subtitleOffset = titleCollapseFraction * -2.0 } - let rawTitleFrame = titleFrame + let rawTitleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? 0.0 : titleHorizontalOffset * titleScale, dy: 0.0) self.titleNodeRawContainer.frame = rawTitleFrame transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(), size: CGSize())) let rawSubtitleFrame = subtitleFrame