diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 784a68f5c3..02a9daae4c 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -514,6 +514,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true self.textInputNode = textInputNode + textInputNode.textView.inputAssistantItem.leadingBarButtonGroups = [] + textInputNode.textView.inputAssistantItem.trailingBarButtonGroups = [] + if let presentationInterfaceState = self.presentationInterfaceState { refreshChatTextInputTypingAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index d74a51a6ae..5c773e805d 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/SemanticStatusNode:SemanticStatusNode", "//submodules/MoreButtonNode:MoreButtonNode", "//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index bdc2e56aca..ca9f74c8b7 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -16,6 +16,7 @@ import PhotoResources import AnimatedStickerComponent import SemanticStatusNode import MediaResources +import MultilineTextComponent private let buttonSize = CGSize(width: 88.0, height: 49.0) private let smallButtonWidth: CGFloat = 69.0 @@ -162,7 +163,7 @@ private final class AttachButtonComponent: CombinedComponent { static var body: Body { let icon = Child(IconComponent.self) let animatedIcon = Child(AnimatedStickerComponent.self) - let title = Child(Text.self) + let title = Child(MultilineTextComponent.self) let button = Child(Rectangle.self) return { context in @@ -257,10 +258,15 @@ private final class AttachButtonComponent: CombinedComponent { } let title = title.update( - component: Text( - text: name, - font: Font.regular(10.0), - color: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: name, + font: Font.regular(10.0), + textColor: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, + paragraphAlignment: .center)), + horizontalAlignment: .center, + truncationType: .end, + maximumNumberOfLines: 1 ), availableSize: context.availableSize, transition: .immediate diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index fad3844aea..990bda668d 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -180,6 +180,7 @@ public final class BotCheckoutController: ViewController { guard !self.didCancel && !self.didFail && !self.didComplete else { return } + self.didCancel = true self.cancelled() } @@ -188,6 +189,7 @@ public final class BotCheckoutController: ViewController { guard !self.didCancel && !self.didFail && !self.didComplete else { return } + self.didFail = true self.failed() } @@ -196,6 +198,7 @@ public final class BotCheckoutController: ViewController { guard !self.didCancel && !self.didFail && !self.didComplete else { return } + self.didComplete = true self.completed(currencyValue, receiptMessageId) } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 9a0703360e..17c73da3c7 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -1299,8 +1299,8 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } if success { - strongSelf.dismissAnimated() strongSelf.completed(currencyValue, receiptMessageId) + strongSelf.dismissAnimated() } else { strongSelf.dismissAnimated() } diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 2c2e2f66e6..ba3747dd80 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -354,25 +354,36 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch case let .limitExceeded(count, _): f(.default) - if case .filter = location { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: { - let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) - replaceImpl?(premiumScreen) - }) - chatListController?.push(controller) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) + let isPremium = limitsData.0?.isPremium ?? false + if isPremium { + if case .filter = location { + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {}) + chatListController?.push(controller) + } else { + let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {}) + chatListController?.push(controller) } } else { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: { - let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) - replaceImpl?(premiumScreen) - }) - chatListController?.push(controller) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) + if case .filter = location { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: { + let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(premiumScreen) + }) + chatListController?.push(controller) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + } else { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: { + let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(premiumScreen) + }) + chatListController?.push(controller) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index e78ae7392b..6630b2a315 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -864,18 +864,35 @@ public final class ChatListNode: ListView { break case let .limitExceeded(count, _): if isPremium { - let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {}) - strongSelf.push?(controller) - } else { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: { - let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) - replaceImpl?(premiumScreen) - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) + if case .filter = location { + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {}) + strongSelf.push?(controller) + } else { + let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {}) + strongSelf.push?(controller) + } + } else { + if case .filter = location { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: { + let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(premiumScreen) + }) + strongSelf.push?(controller) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + } else { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: { + let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(premiumScreen) + }) + strongSelf.push?(controller) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } } - strongSelf.push?(controller) } } } diff --git a/submodules/Display/Source/DeviceMetrics.swift b/submodules/Display/Source/DeviceMetrics.swift index 4d01d7ac2c..1b35f94e01 100644 --- a/submodules/Display/Source/DeviceMetrics.swift +++ b/submodules/Display/Source/DeviceMetrics.swift @@ -180,7 +180,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { } } - func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> CGFloat? { + public func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> CGFloat? { switch self { case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax: return inLandscape ? 21.0 : 34.0 diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index 7a4239e128..186bf3489b 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -490,7 +490,16 @@ public class Window1 { self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self { + var isTablet = false + if case .regular = strongSelf.windowLayout.metrics.widthClass { + isTablet = true + } + var keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect() + if isTablet && keyboardFrame.isEmpty { + return + } + if #available(iOSApplicationExtension 14.2, iOS 14.2, *), UIAccessibility.prefersCrossFadeTransitions { } else if let keyboardView = strongSelf.statusBarHost?.keyboardView { if keyboardFrame.width.isEqual(to: keyboardView.bounds.width) && keyboardFrame.height.isEqual(to: keyboardView.bounds.height) && keyboardFrame.minX.isEqual(to: keyboardView.frame.minX) { @@ -540,7 +549,11 @@ public class Window1 { var keyboardHeight: CGFloat if keyboardFrame.isEmpty || keyboardFrame.maxY < screenHeight { - keyboardHeight = 0.0 + if isTablet && screenHeight - keyboardFrame.maxY < 5.0 { + keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY) + } else { + keyboardHeight = 0.0 + } } else { keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY) if inPopover && !keyboardHeight.isZero { @@ -1119,7 +1132,7 @@ public class Window1 { if let image = self.badgeView.image { self.updateBadgeVisibility() - self.badgeView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.windowLayout.size.width - image.size.width) / 2.0), y: 6.0), size: image.size) + self.badgeView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.windowLayout.size.width - image.size.width) / 2.0), y: 5.0), size: image.size) } } } 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/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index ee8e04a8db..0d275e10d8 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -65,6 +65,8 @@ typedef enum - (void)setEditButtonsHighlighted:(TGPhotoEditorTab)buttons; - (void)setEditButtonsDisabled:(TGPhotoEditorTab)buttons; +- (void)setAllButtonsHidden:(bool)hidden animated:(bool)animated; + @property (nonatomic, readonly) TGPhotoEditorTab currentTabs; - (void)setToolbarTabs:(TGPhotoEditorTab)tabs animated:(bool)animated; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index dbc2caa921..12ad1135ac 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -333,6 +333,12 @@ if (strongSelf == nil) return; + if (keyboardHeight > 0) { + [strongSelf->_portraitToolbarView setAllButtonsHidden:true animated:true]; + } else { + [strongSelf->_portraitToolbarView setAllButtonsHidden:false animated:true]; + } + CGFloat offset = 0.0f; if (keyboardHeight > 0) offset = -keyboardHeight / 2.0f; diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index cd9460a9ea..4014752b9c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -239,7 +239,7 @@ if (_keyboardHeight > 0.0) { backgroundHeight += _keyboardHeight - edgeInsets.bottom; } - _backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight); + _backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight + 1.0); } @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m index 77134e7bfe..f652cccb86 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m @@ -54,7 +54,6 @@ [_cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [_backgroundView addSubview:_cancelButton]; - _doneButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, buttonSize.width, buttonSize.height)]; _doneButton.exclusiveTouch = true; _doneButton.adjustsImageWhenHighlighted = false; @@ -495,6 +494,40 @@ } } +- (void)setAllButtonsHidden:(bool)hidden animated:(bool)animated +{ + CGFloat targetAlpha = hidden ? 0.0f : 1.0f; + + if (animated) + { + _buttonsWrapperView.hidden = false; + _cancelButton.hidden = false; + _doneButton.hidden = false; + + [UIView animateWithDuration:0.2f + animations:^ + { + _buttonsWrapperView.alpha = targetAlpha; + _cancelButton.alpha = targetAlpha; + _doneButton.alpha = targetAlpha; + } completion:^(__unused BOOL finished) + { + _buttonsWrapperView.hidden = hidden; + _cancelButton.hidden = hidden; + _doneButton.hidden = hidden; + }]; + } + else + { + _buttonsWrapperView.alpha = targetAlpha; + _cancelButton.alpha = targetAlpha; + _doneButton.alpha = targetAlpha; + _buttonsWrapperView.hidden = hidden; + _cancelButton.hidden = hidden; + _doneButton.hidden = hidden; + } +} + - (TGPhotoEditorButton *)buttonForTab:(TGPhotoEditorTab)tab { for (TGPhotoEditorButton *button in _buttonsWrapperView.subviews) diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index a822040022..6cdc49955a 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -287,6 +287,12 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext { safeInsets.bottom = 21.0 } else if validLayout.intrinsicInsets.bottom.isEqual(to: 34.0) { safeInsets.bottom = 34.0 + } else { + if let knownSafeInset = validLayout.deviceMetrics.onScreenNavigationHeight(inLandscape: validLayout.size.width > validLayout.size.height, systemOnScreenNavigationHeight: nil) { + if knownSafeInset > 0.0 { + safeInsets.bottom = knownSafeInset + } + } } if controller.navigationPresentation == .modal { safeInsets.top = 0.0 diff --git a/submodules/PremiumUI/Resources/swirl.scn b/submodules/PremiumUI/Resources/swirl.scn index affcd2a748..8811158627 100644 Binary files a/submodules/PremiumUI/Resources/swirl.scn and b/submodules/PremiumUI/Resources/swirl.scn differ diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/PremiumUI/Sources/PremiumStarComponent.swift index 4e1c3b3047..92bf12d1e2 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumStarComponent.swift @@ -208,15 +208,15 @@ class PremiumStarComponent: Component { "rotate", "tapRotate" ] - if #available(iOS 11.0, *) { - for key in keys { - node.removeAnimation(forKey: key, blendOutDuration: 0.1) - } - } else { +// if #available(iOS 11.0, *) { +// for key in keys { +// node.removeAnimation(forKey: key, blendOutDuration: 0.1) +// } +// } else { for key in keys { node.removeAnimation(forKey: key) } - } +// } switch gesture.state { case .began: @@ -535,9 +535,9 @@ class PremiumStarComponent: Component { let to = SCNVector3(x: 0.0, y: toValue, z: 0.0) let distance = rad2deg(to.y - from.y) -// guard !distance.isZero else { -// return -// } + guard !distance.isZero else { + return + } let springAnimation = CASpringAnimation(keyPath: "eulerAngles") springAnimation.fromValue = NSValue(scnVector3: from) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 8afc0dd85f..cdd0e71930 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -123,13 +123,9 @@ public struct ChatListFilterIncludePeers: Equatable, Hashable { self.pinnedPeers.insert(peerId, at: 0) return true } else { - if self.peers.count < 100 { - self.peers.insert(peerId, at: 0) - self.pinnedPeers.insert(peerId, at: 0) - return true - } else { - return false - } + self.peers.insert(peerId, at: 0) + self.pinnedPeers.insert(peerId, at: 0) + return true } } @@ -217,10 +213,7 @@ public struct ChatListFilterData: Equatable, Hashable { if self.excludePeers.contains(peerId) { return false } - if self.excludePeers.count >= 100 { - return false - } - + let _ = self.includePeers.removePeer(peerId) self.excludePeers.append(peerId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift index 9b9e204345..9a903484eb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift @@ -42,9 +42,6 @@ func _internal_toggleItemPinned(postbox: Postbox, accountPeerId: PeerId, locatio additionalCount = 1 } - - - let limitCount: Int if case .root = groupId { limitCount = Int(userLimitsConfiguration.maxPinnedChatCount) @@ -76,8 +73,10 @@ func _internal_toggleItemPinned(postbox: Postbox, accountPeerId: PeerId, locatio if updatedData.includePeers.pinnedPeers.contains(peerId) { updatedData.includePeers.removePinnedPeer(peerId) } else { - if !updatedData.includePeers.addPinnedPeer(peerId) { + let _ = updatedData.includePeers.addPinnedPeer(peerId) + if updatedData.includePeers.peers.count > userLimitsConfiguration.maxFolderChatsCount { result = .limitExceeded(count: updatedData.includePeers.peers.count, limit: Int(userLimitsConfiguration.maxFolderChatsCount)) + updatedData = data } } filters[index] = .filter(id: id, title: title, emoticon: emoticon, data: updatedData) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bd4c9cef98..3927111dd0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -15537,8 +15537,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) if actions.contains(3) { - let context = strongSelf.context - let _ = context.engine.messages.deleteAllMessagesWithAuthor(peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud).start() + let _ = strongSelf.context.engine.messages.deleteAllMessagesWithAuthor(peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud).start() let _ = strongSelf.context.engine.messages.clearAuthorHistory(peerId: peerId, memberId: author.id).start() } else if actions.contains(0) { let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start() diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 810a28551a..1243c3130f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1482,7 +1482,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let inputBackgroundInset: CGFloat if cleanInsets.bottom < insets.bottom { - inputBackgroundInset = 0.0 + if case .regular = layout.metrics.widthClass, insets.bottom < 88.0 { + inputBackgroundInset = insets.bottom + } else { + inputBackgroundInset = 0.0 + } } else { inputBackgroundInset = cleanInsets.bottom } diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index f710cacb55..291fe52dab 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -109,6 +109,7 @@ class ChatMessageShareButton: HighlightableButtonNode { var updatedIconOffset = CGPoint() if case .pinnedMessages = subject { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else if isReplies { updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { @@ -976,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 @@ -1923,6 +1924,34 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } } + + if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) { + if let item = self.item, let forwardInfo = item.message.forwardInfo { + let performAction: () -> Void = { + if let sourceMessageId = forwardInfo.sourceMessageId { + if !item.message.id.peerId.isReplies, let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { + if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { + } else if case .member = channel.participationStatus { + } else { + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + return + } + } + item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId) + } else if let peer = forwardInfo.source ?? forwardInfo.author { + item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, nil) + } else if let _ = forwardInfo.authorSignature { + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) + } + } + + if forwardInfoNode.hasAction(at: self.view.convert(location, to: forwardInfoNode.view)) { + return .action({}) + } else { + return .optionalAction(performAction) + } + } + } if let item = self.item, self.imageNode.frame.contains(location) { let emojiTapAction: (Bool) -> InternalBubbleTapAction? = { shouldPlay in 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/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift index 880b7a3d38..791bfc89b3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift @@ -213,7 +213,7 @@ class ChatMessageForwardInfoNode: ASDisplayNode { highlight = false } - let completeString: NSString = completeSourceString.string as NSString + let completeString: NSString = (completeSourceString.string.replacingOccurrences(of: "\n", with: " \n")) as NSString let string = NSMutableAttributedString(string: completeString as String, attributes: [NSAttributedString.Key.foregroundColor: titleColor, NSAttributedString.Key.font: prefixFont]) if highlight, let range = completeSourceString.ranges.first?.range { string.addAttributes([NSAttributedString.Key.font: peerFont], range: range) 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/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index c07d3aa24c..cb4bcc5e94 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -83,6 +83,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private let queue = Queue() override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let buttonResult = self.buttonsContainer.hitTest(point.offsetBy(dx: -self.buttonsContainer.frame.minX, dy: -self.buttonsContainer.frame.minY), with: event) { + return buttonResult + } let containerResult = self.contentTextContainer.hitTest(point.offsetBy(dx: -self.contentTextContainer.frame.minX, dy: -self.contentTextContainer.frame.minY), with: event) if containerResult?.asyncdisplaykit_node === self.dustNode, self.dustNode?.isRevealed == false { return containerResult 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 {