diff --git a/submodules/InAppPurchaseManager/BUILD b/submodules/InAppPurchaseManager/BUILD index 03bc8fd60e..8cbb34b326 100644 --- a/submodules/InAppPurchaseManager/BUILD +++ b/submodules/InAppPurchaseManager/BUILD @@ -14,6 +14,8 @@ swift_library( "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/PersistentStringHash:PersistentStringHash", ], visibility = [ "//visibility:public", diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 2880d2b99a..1f4e6b248e 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -5,6 +5,8 @@ import StoreKit import Postbox import TelegramCore import TelegramStringFormatting +import TelegramUIPreferences +import PersistentStringHash private let productIdentifiers = [ "org.telegram.telegramPremium.annual", @@ -14,6 +16,10 @@ private let productIdentifiers = [ "org.telegram.telegramPremium.threeMonths" ] +private func isSubscriptionProductId(_ id: String) -> Bool { + return id.hasSuffix(".monthly") || id.hasSuffix(".annual") +} + private extension NSDecimalNumber { func round(_ decimals: Int) -> NSDecimalNumber { return self.rounding(accordingToBehavior: @@ -322,6 +328,16 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { case .purchasing: Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") purchasing") transactionState = .purchasing + if let paymentContext = self.paymentContexts[transaction.payment.productIdentifier] { + let _ = updatePendingInAppPurchaseState( + engine: self.engine, + productId: transaction.payment.productIdentifier, + content: PendingInAppPurchaseState( + productId: transaction.payment.productIdentifier, + targetPeerId: paymentContext.targetPeerId + ) + ).start() + } case .deferred: Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") deferred") transactionState = .deferred @@ -339,29 +355,52 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { let transactionIds = transactionsToAssign.compactMap({ $0.transactionIdentifier }).joined(separator: ", ") Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), sending receipt for transactions [\(transactionIds)]") - let transaction = transactionsToAssign.first - let purposeSignal: Signal - if let productIdentifier = transaction?.payment.productIdentifier, let targetPeerId = paymentContexts[productIdentifier]?.targetPeerId { - purposeSignal = self.availableProducts + guard let transaction = transactionsToAssign.first else { + return + } + let productIdentifier = transaction.payment.productIdentifier + + var completion: Signal = .never() + + let purpose: Signal + if !isSubscriptionProductId(productIdentifier) { + let peerId: Signal + if let targetPeerId = paymentContexts[productIdentifier]?.targetPeerId { + peerId = .single(targetPeerId) + } else { + peerId = pendingInAppPurchaseState(engine: self.engine, productId: productIdentifier) + |> mapToSignal { state -> Signal in + if let state = state, let peerId = state.targetPeerId { + return .single(peerId) + } else { + return .complete() + } + } + } + completion = updatePendingInAppPurchaseState(engine: self.engine, productId: productIdentifier, content: nil) + + let products = self.availableProducts |> filter { products in return !products.isEmpty } |> take(1) - |> map { products -> AppStoreTransactionPurpose in + + purpose = combineLatest(products, peerId) + |> map { products, peerId -> AppStoreTransactionPurpose in if let product = products.first(where: { $0.id == productIdentifier }) { let (currency, amount) = product.priceCurrencyAndAmount - return .gift(peerId: targetPeerId, currency: currency, amount: amount) + return .gift(peerId: peerId, currency: currency, amount: amount) } else { - return .gift(peerId: targetPeerId, currency: "", amount: 0) + return .gift(peerId: peerId, currency: "", amount: 0) } } } else { - purposeSignal = .single(.subscription) + purpose = .single(.subscription) } let receiptData = getReceiptData() ?? Data() self.disposableSet.set( - (purposeSignal + (purpose |> castError(AssignAppStoreTransactionError.self) |> mapToSignal { purpose -> Signal in self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose) @@ -380,6 +419,8 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { for transaction in transactions { queue.finishTransaction(transaction) } + + let _ = completion.start() }), forKey: transactionIds ) @@ -428,3 +469,50 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } } } + +private final class PendingInAppPurchaseState: Codable { + public let productId: String + public let targetPeerId: PeerId? + + public init(productId: String, targetPeerId: PeerId?) { + self.productId = productId + self.targetPeerId = targetPeerId + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.productId = try container.decode(String.self, forKey: "productId") + self.targetPeerId = (try container.decodeIfPresent(Int64.self, forKey: "targetPeerId")).flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.productId, forKey: "productId") + if let targetPeerId = self.targetPeerId { + try container.encode(targetPeerId.id._internalGetInt64Value(), forKey: "targetPeerId") + } + } +} + +private func pendingInAppPurchaseState(engine: TelegramEngine, productId: String) -> Signal { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue)) + + return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)) + |> map { entry -> PendingInAppPurchaseState? in + return entry?.get(PendingInAppPurchaseState.self) + } +} + +private func updatePendingInAppPurchaseState(engine: TelegramEngine, productId: String, content: PendingInAppPurchaseState?) -> Signal { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue)) + + if let content = content { + return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content) + } else { + return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key) + } +} diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 8a259a6c76..291fe52dab 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -977,7 +977,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } if item.message.forwardInfo != nil || item.message.attributes.first(where: { $0 is ReplyMessageAttribute }) != nil { - tmpWidth -= 60.0 + tmpWidth -= 45.0 } tmpWidth -= deliveryFailedInset diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index 65f8555bd5..939444c7a0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -521,17 +521,20 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { } })) - let credibilityIconNode: ASImageNode - if let current = self.credibilityIconNode { - credibilityIconNode = current - } else { - credibilityIconNode = ASImageNode() - credibilityIconNode.displaysAsynchronously = false - credibilityIconNode.displayWithoutProcessing = true - credibilityIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) - self.containerNode.addSubnode(credibilityIconNode) + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !premiumConfiguration.isPremiumDisabled { + let credibilityIconNode: ASImageNode + if let current = self.credibilityIconNode { + credibilityIconNode = current + } else { + credibilityIconNode = ASImageNode() + credibilityIconNode.displaysAsynchronously = false + credibilityIconNode.displayWithoutProcessing = true + credibilityIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) + self.containerNode.addSubnode(credibilityIconNode) + } + credibilityIconNode.frame = CGRect(origin: CGPoint(x: 29.0 - UIScreenPixel, y: 29.0 - UIScreenPixel), size: CGSize(width: 10.0, height: 10.0)) } - credibilityIconNode.frame = CGRect(origin: CGPoint(x: 29.0 - UIScreenPixel, y: 29.0 - UIScreenPixel), size: CGSize(width: 10.0, height: 10.0)) } else { self.credibilityIconNode?.removeFromSupernode() self.credibilityIconNode = nil diff --git a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift index f0287d959b..73f5a25956 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift @@ -443,7 +443,8 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.animationNode.playOnce() } - if !alreadySeen { + if !alreadySeen && self.animationNode.isPlaying { + item.controllerInteraction.playNextOutgoingGift = false Queue.mainQueue().after(1.0) { item.controllerInteraction.animateDiceSuccess(false, true) } diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index 5fde289241..da4427fe3f 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -68,6 +68,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { case cachedGeocodes = 4 case visualMediaStoredState = 5 case cachedImageRecognizedContent = 6 + case pendingInAppPurchaseState = 7 } public struct ApplicationSpecificItemCacheCollectionId { @@ -78,6 +79,7 @@ public struct ApplicationSpecificItemCacheCollectionId { public static let cachedGeocodes = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedGeocodes.rawValue) public static let visualMediaStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.visualMediaStoredState.rawValue) public static let cachedImageRecognizedContent = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedImageRecognizedContent.rawValue) + public static let pendingInAppPurchaseState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.pendingInAppPurchaseState.rawValue) } private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {