diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 679dc94785..26be1b2e9a 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7957,3 +7957,7 @@ Sorry for the inconvenience."; "KeyCommand.ExitFullscreen" = "Exit Fullscreen"; "StickerPacksSettings.SuggestAnimatedEmoji" = "Suggest Animated Emoji"; + +"Emoji.FrequentlyUsed" = "Recently Used"; + +"Checkout.PaymentLiabilityBothAlert" = "Telegram will not have access to your credit card information. Credit card details will be handled only by the payment system, {target}.\n\nPayments will go directly to the developer of {target}. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of {target} or your bank."; 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..5e33e8e693 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() } @@ -1464,10 +1464,16 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz if value { strongSelf.pay(savedCredentialsToken: savedCredentialsToken, liabilityNoticeAccepted: true) } else { - let paymentText = strongSelf.presentationData.strings.Checkout_PaymentLiabilityAlert + let paymentText: String + if botPeer.id == providerPeer?.id { + paymentText = strongSelf.presentationData.strings.Checkout_PaymentLiabilityBothAlert + .replacingOccurrences(of: "{target}", with: botPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)) + } else { + paymentText = strongSelf.presentationData.strings.Checkout_PaymentLiabilityAlert .replacingOccurrences(of: "{target}", with: botPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)) .replacingOccurrences(of: "{payment_system}", with: providerPeer?.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder) ?? "") - + } + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Checkout_LiabilityAlertTitle, text: paymentText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { if let strongSelf = self { let _ = ApplicationSpecificNotice.setBotPaymentLiability(accountManager: strongSelf.context.sharedContext.accountManager, peerId: paymentForm.paymentBotId).start() 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 03ba28e906..e5072260a7 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.monthly", @@ -13,6 +15,10 @@ private let productIdentifiers = [ "org.telegram.telegramPremium.threeMonths" ] +private func isSubscriptionProductId(_ id: String) -> Bool { + return id.hasSuffix(".monthly") +} + private extension NSDecimalNumber { func round(_ decimals: Int) -> NSDecimalNumber { return self.rounding(accordingToBehavior: @@ -321,6 +327,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 @@ -338,29 +354,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) @@ -379,6 +418,8 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { for transaction in transactions { queue.finishTransaction(transaction) } + + let _ = completion.start() }), forKey: transactionIds ) @@ -427,3 +468,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/InAppPurchaseManager/Sources/StoredTransactionState.swift b/submodules/InAppPurchaseManager/Sources/StoredTransactionState.swift deleted file mode 100644 index 3f0f328d81..0000000000 --- a/submodules/InAppPurchaseManager/Sources/StoredTransactionState.swift +++ /dev/null @@ -1,57 +0,0 @@ -//import Foundation -//import UIKit -//import SwiftSignalKit -//import Postbox -//import TelegramCore -//import TelegramUIPreferences -// -//final class StoredTransactionState: Codable { -// let timestamp: Double -// let playbackRate: AudioPlaybackRate -// -// init(timestamp: Double, playbackRate: AudioPlaybackRate) { -// self.timestamp = timestamp -// self.playbackRate = playbackRate -// } -// -// public init(from decoder: Decoder) throws { -// let container = try decoder.container(keyedBy: StringCodingKey.self) -// -// self.timestamp = try container.decode(Double.self, forKey: "timestamp") -// self.playbackRate = AudioPlaybackRate(rawValue: try container.decode(Int32.self, forKey: "playbackRate")) ?? .x1 -// } -// -// public func encode(to encoder: Encoder) throws { -// var container = encoder.container(keyedBy: StringCodingKey.self) -// -// try container.encode(self.timestamp, forKey: "timestamp") -// try container.encode(self.playbackRate.rawValue, forKey: "playbackRate") -// } -//} -// -//public func storedState(engine: TelegramEngine, : MessageId) -> Signal { -// let key = ValueBoxKey(length: 20) -// key.setInt32(0, value: messageId.namespace) -// key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) -// key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) -// key.setInt32(16, value: messageId.id) -// -// return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, id: key)) -// |> map { entry -> MediaPlaybackStoredState? in -// return entry?.get(MediaPlaybackStoredState.self) -// } -//} -// -//public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: MessageId, state: MediaPlaybackStoredState?) -> Signal { -// let key = ValueBoxKey(length: 20) -// key.setInt32(0, value: messageId.namespace) -// key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) -// key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) -// key.setInt32(16, value: messageId.id) -// -// if let state = state { -// return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, id: key, item: state) -// } else { -// return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, 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/emoji.scn b/submodules/PremiumUI/Resources/emoji.scn index 26255185bd..f4f25b3ba7 100644 Binary files a/submodules/PremiumUI/Resources/emoji.scn and b/submodules/PremiumUI/Resources/emoji.scn differ diff --git a/submodules/PremiumUI/Resources/gift.scn b/submodules/PremiumUI/Resources/gift.scn index 14f72e0681..45ed4e7c0d 100644 Binary files a/submodules/PremiumUI/Resources/gift.scn and b/submodules/PremiumUI/Resources/gift.scn differ diff --git a/submodules/PremiumUI/Resources/smilie.png b/submodules/PremiumUI/Resources/smilie.png index 8de30d8842..32726e505d 100644 Binary files a/submodules/PremiumUI/Resources/smilie.png and b/submodules/PremiumUI/Resources/smilie.png differ diff --git a/submodules/PremiumUI/Resources/star.scn b/submodules/PremiumUI/Resources/star.scn index 9766b2b264..a646c9e70b 100644 Binary files a/submodules/PremiumUI/Resources/star.scn and b/submodules/PremiumUI/Resources/star.scn differ diff --git a/submodules/PremiumUI/Resources/sunglasses.png b/submodules/PremiumUI/Resources/sunglasses.png index 87f51368ba..1c0202af1d 100644 Binary files a/submodules/PremiumUI/Resources/sunglasses.png and b/submodules/PremiumUI/Resources/sunglasses.png differ 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/Resources/thumbsup.png b/submodules/PremiumUI/Resources/thumbsup.png index 3b13d58b2f..5279330d5a 100644 Binary files a/submodules/PremiumUI/Resources/thumbsup.png and b/submodules/PremiumUI/Resources/thumbsup.png differ diff --git a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift index 668a2e6a1f..c49f4f0721 100644 --- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift +++ b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift @@ -108,10 +108,7 @@ class GiftAvatarComponent: Component { self.addSubview(self.avatarNode.view) self.setup() - - let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) - self.addGestureRecognizer(panGestureRecoginzer) - + let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) self.addGestureRecognizer(tapGestureRecoginzer) @@ -134,58 +131,6 @@ class GiftAvatarComponent: Component { self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true) } - private var previousYaw: Float = 0.0 - @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { - guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { - return - } - - self.previousInteractionTimestamp = CACurrentMediaTime() - - if #available(iOS 11.0, *) { - node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1) - node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1) - } else { - node.removeAllAnimations() - } - - switch gesture.state { - case .began: - self.previousYaw = 0.0 - case .changed: - let translation = gesture.translation(in: gesture.view) - let yawPan = deg2rad(Float(translation.x)) - - func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat { - let bandedOffset = offset - bandingStart - let range: CGFloat = 60.0 - let coefficient: CGFloat = 0.4 - return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range - } - - var pitchTranslation = rubberBandingOffset(offset: abs(translation.y), bandingStart: 0.0) - if translation.y < 0.0 { - pitchTranslation *= -1.0 - } - let pitchPan = deg2rad(Float(pitchTranslation)) - - self.previousYaw = yawPan - node.eulerAngles = SCNVector3(pitchPan, yawPan, 0.0) - case .ended: - let velocity = gesture.velocity(in: gesture.view) - - var smallAngle = false - if (self.previousYaw < .pi / 2 && self.previousYaw > -.pi / 2) && abs(velocity.x) < 200 { - smallAngle = true - } - - self.playAppearanceAnimation(velocity: velocity.x, smallAngle: smallAngle, explode: !smallAngle && abs(velocity.x) > 600) - node.eulerAngles = SCNVector3(0.0, 0.0, 0.0) - default: - break - } - } - private func setup() { guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { return @@ -210,6 +155,8 @@ class GiftAvatarComponent: Component { } private func onReady() { + self.setupScaleAnimation() + self.playAppearanceAnimation(explode: true) self.previousInteractionTimestamp = CACurrentMediaTime() @@ -224,6 +171,18 @@ class GiftAvatarComponent: Component { self.timer?.start() } + private func setupScaleAnimation() { + let animation = CABasicAnimation(keyPath: "transform.scale") + animation.duration = 2.0 + animation.fromValue = 1.0 //NSValue(scnVector3: SCNVector3(x: 0.1, y: 0.1, z: 0.1)) + animation.toValue = 1.15 //NSValue(scnVector3: SCNVector3(x: 0.115, y: 0.115, z: 0.115)) + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + animation.autoreverses = true + animation.repeatCount = .infinity + + self.avatarNode.view.layer.add(animation, forKey: "scale") + } + private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) { guard let scene = self.sceneView.scene else { return @@ -233,23 +192,50 @@ class GiftAvatarComponent: Component { self.previousInteractionTimestamp = currentTime self.delayTapsTill = currentTime + 0.85 - if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) { - if let particleSystem = particles.particleSystems?.first { - particleSystem.particleColorVariation = SCNVector4(0.15, 0.2, 0.15, 0.3) - particleSystem.speedFactor = 2.0 - particleSystem.particleVelocity = 2.2 - particleSystem.birthRate = 4.0 - particleSystem.particleLifeSpan = 2.0 + if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) { + if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first { + leftParticleSystem.speedFactor = 2.0 + leftParticleSystem.particleVelocity = 1.6 + leftParticleSystem.birthRate = 60.0 + leftParticleSystem.particleLifeSpan = 4.0 + + rightParticleSystem.speedFactor = 2.0 + rightParticleSystem.particleVelocity = 1.6 + rightParticleSystem.birthRate = 60.0 + rightParticleSystem.particleLifeSpan = 4.0 + +// leftBottomParticleSystem.speedFactor = 2.0 + leftBottomParticleSystem.particleVelocity = 1.6 + leftBottomParticleSystem.birthRate = 24.0 + leftBottomParticleSystem.particleLifeSpan = 7.0 + +// rightBottomParticleSystem.speedFactor = 2.0 + rightBottomParticleSystem.particleVelocity = 1.6 + rightBottomParticleSystem.birthRate = 24.0 + rightBottomParticleSystem.particleLifeSpan = 7.0 node.physicsField?.isActive = true Queue.mainQueue().after(1.0) { node.physicsField?.isActive = false - particles.particleSystems?.first?.birthRate = 1.2 - particleSystem.particleVelocity = 1.0 - particleSystem.particleLifeSpan = 4.0 - let animation = POPBasicAnimation() - animation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + leftParticleSystem.birthRate = 12.0 + leftParticleSystem.particleVelocity = 1.2 + leftParticleSystem.particleLifeSpan = 3.0 + + rightParticleSystem.birthRate = 12.0 + rightParticleSystem.particleVelocity = 1.2 + rightParticleSystem.particleLifeSpan = 3.0 + + leftBottomParticleSystem.particleVelocity = 1.2 + leftBottomParticleSystem.birthRate = 7.0 + leftBottomParticleSystem.particleLifeSpan = 5.0 + + rightBottomParticleSystem.particleVelocity = 1.2 + rightBottomParticleSystem.birthRate = 7.0 + rightBottomParticleSystem.particleLifeSpan = 5.0 + + let leftAnimation = POPBasicAnimation() + leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in property?.readBlock = { particleSystem, values in values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor } @@ -258,11 +244,27 @@ class GiftAvatarComponent: Component { } property?.threshold = 0.01 }) as! POPAnimatableProperty) - animation.fromValue = 2.0 as NSNumber - animation.toValue = 1.0 as NSNumber - animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - animation.duration = 0.5 - particleSystem.pop_add(animation, forKey: "speedFactor") + leftAnimation.fromValue = 1.2 as NSNumber + leftAnimation.toValue = 0.85 as NSNumber + leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + leftAnimation.duration = 0.5 + leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor") + + let rightAnimation = POPBasicAnimation() + rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + property?.readBlock = { particleSystem, values in + values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor + } + property?.writeBlock = { particleSystem, values in + (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty) + rightAnimation.fromValue = 1.2 as NSNumber + rightAnimation.toValue = 0.85 as NSNumber + rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + rightAnimation.duration = 0.5 + rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor") } } } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index b0305e0722..f569953323 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1441,8 +1441,13 @@ private final class PremiumIntroScreenComponent: CombinedComponent { starIsVisible = false } + var isIntro = true + if case .profile = context.component.source { + isIntro = false + } + let star = star.update( - component: PremiumStarComponent(isVisible: starIsVisible, hasIdleAnimations: state.hasIdleAnimations), + component: PremiumStarComponent(isIntro: isIntro, isVisible: starIsVisible, hasIdleAnimations: state.hasIdleAnimations), availableSize: CGSize(width: min(390.0, context.availableSize.width), height: 220.0), transition: context.transition ) diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/PremiumUI/Sources/PremiumStarComponent.swift index d679668719..f2948a401c 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumStarComponent.swift @@ -19,7 +19,7 @@ private func rad2deg(_ number: Float) -> Float { } private func generateParticlesTexture() -> UIImage { - return UIImage() + return UIImage() } private func generateFlecksTexture() -> UIImage { @@ -46,16 +46,18 @@ private func generateDiffuseTexture() -> UIImage { } class PremiumStarComponent: Component { + let isIntro: Bool let isVisible: Bool let hasIdleAnimations: Bool - init(isVisible: Bool, hasIdleAnimations: Bool) { + init(isIntro: Bool, isVisible: Bool, hasIdleAnimations: Bool) { + self.isIntro = isIntro self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations } static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool { - return lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations + return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations } final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { @@ -84,7 +86,11 @@ class PremiumStarComponent: Component { private var timer: SwiftSignalKit.Timer? private var hasIdleAnimations = false - override init(frame: CGRect) { + private let isIntro: Bool + + init(frame: CGRect, isIntro: Bool) { + self.isIntro = isIntro + self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0))) self.sceneView.backgroundColor = .clear self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) @@ -202,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: @@ -369,10 +375,13 @@ class PremiumStarComponent: Component { return } + let fromScale: Float = self.isIntro ? 0.1 : 0.08 + let toScale: Float = self.isIntro ? 0.115 : 0.092 + let animation = CABasicAnimation(keyPath: "scale") animation.duration = 2.0 - animation.fromValue = NSValue(scnVector3: SCNVector3(x: 0.1, y: 0.1, z: 0.1)) - animation.toValue = NSValue(scnVector3: SCNVector3(x: 0.115, y: 0.115, z: 0.115)) + animation.fromValue = NSValue(scnVector3: SCNVector3(x: fromScale, y: fromScale, z: fromScale)) + animation.toValue = NSValue(scnVector3: SCNVector3(x: toScale, y: toScale, z: toScale)) animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) animation.autoreverses = true animation.repeatCount = .infinity @@ -433,30 +442,48 @@ class PremiumStarComponent: Component { self.previousInteractionTimestamp = currentTime self.delayTapsTill = currentTime + 0.85 - if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false) { - if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first { - leftParticleSystem.speedFactor = 1.3 - leftParticleSystem.particleVelocity = 2.4 - leftParticleSystem.birthRate = 24.0 + if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) { + if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first { + leftParticleSystem.speedFactor = 2.0 + leftParticleSystem.particleVelocity = 1.6 + leftParticleSystem.birthRate = 60.0 leftParticleSystem.particleLifeSpan = 4.0 - rightParticleSystem.speedFactor = 1.3 - rightParticleSystem.particleVelocity = 2.4 - rightParticleSystem.birthRate = 24.0 + rightParticleSystem.speedFactor = 2.0 + rightParticleSystem.particleVelocity = 1.6 + rightParticleSystem.birthRate = 60.0 rightParticleSystem.particleLifeSpan = 4.0 +// leftBottomParticleSystem.speedFactor = 2.0 + leftBottomParticleSystem.particleVelocity = 1.6 + leftBottomParticleSystem.birthRate = 24.0 + leftBottomParticleSystem.particleLifeSpan = 7.0 + +// rightBottomParticleSystem.speedFactor = 2.0 + rightBottomParticleSystem.particleVelocity = 1.6 + rightBottomParticleSystem.birthRate = 24.0 + rightBottomParticleSystem.particleLifeSpan = 7.0 + node.physicsField?.isActive = true Queue.mainQueue().after(1.0) { node.physicsField?.isActive = false - leftParticleSystem.birthRate = 9.0 + leftParticleSystem.birthRate = 12.0 leftParticleSystem.particleVelocity = 1.2 leftParticleSystem.particleLifeSpan = 3.0 - rightParticleSystem.birthRate = 9.0 + rightParticleSystem.birthRate = 12.0 rightParticleSystem.particleVelocity = 1.2 rightParticleSystem.particleLifeSpan = 3.0 + leftBottomParticleSystem.particleVelocity = 1.2 + leftBottomParticleSystem.birthRate = 7.0 + leftBottomParticleSystem.particleLifeSpan = 5.0 + + rightBottomParticleSystem.particleVelocity = 1.2 + rightBottomParticleSystem.birthRate = 7.0 + rightBottomParticleSystem.particleLifeSpan = 5.0 + let leftAnimation = POPBasicAnimation() leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in property?.readBlock = { particleSystem, values in @@ -508,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) @@ -541,7 +568,7 @@ class PremiumStarComponent: Component { } func makeView() -> View { - return View(frame: CGRect()) + return View(frame: CGRect(), isIntro: self.isIntro) } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { 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 9d4514d4a0..faf0be6ecb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -15473,8 +15473,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 0146195542..aef7b66e51 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) { @@ -712,7 +713,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if !alreadySeen { item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id) if let emojiString = self.emojiString, emojiString.count == 1 { - self.playAdditionalEmojiAnimation(index: 1) + if item.message.id.peerId.namespace == Namespaces.Peer.CloudUser { + self.playAdditionalEmojiAnimation(index: 1) + } } else if let file = file, file.isPremiumSticker { Queue.mainQueue().after(0.1) { self.playPremiumStickerAnimation() @@ -974,13 +977,13 @@ 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 - let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) - 70.0 - + let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) + let font = Font.regular(fontSizeForEmojiString(item.message.text)) let attributedText = stringWithAppliedEntities(item.message.text, entities: item.message.textEntitiesAttribute?.entities ?? [], baseColor: .black, linkColor: .black, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: item.message) textLayoutAndApply = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural)) @@ -1921,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 @@ -2658,23 +2689,23 @@ private func fontSizeForEmojiString(_ string: String) -> CGFloat { case 1: multiplier = 1.0 case 2: - multiplier = 0.7 + multiplier = 0.84 case 3: - multiplier = 0.52 + multiplier = 0.69 case 4: - multiplier = 0.37 + multiplier = 0.53 case 5: - multiplier = 0.28 + multiplier = 0.46 case 6: - multiplier = 0.25 + multiplier = 0.38 case 7: - multiplier = 0.23 + multiplier = 0.32 case 8: - multiplier = 0.21 + multiplier = 0.27 case 9: - multiplier = 0.19 + multiplier = 0.24 default: - multiplier = 0.19 + multiplier = 0.21 } return floor(basicSize * multiplier) } 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/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 29e075649e..8ccabf6e46 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -647,6 +647,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur var domain: String? var start: String? var startGroup: String? + var startChannel: String? var admin: String? var game: String? var post: String? @@ -684,6 +685,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur voiceChat = "" } else if queryItem.name == "startattach" { startAttach = "" + } else if queryItem.name == "startgroup" { + startGroup = "" + } else if queryItem.name == "startchannel" { + startChannel = "" } } } @@ -698,7 +703,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let start = start { result += "?start=\(start)" } else if let startGroup = startGroup { - result += "?startgroup=\(startGroup)" + if !startGroup.isEmpty { + result += "?startgroup=\(startGroup)" + } else { + result += "?startgroup" + } + if let admin = admin { + result += "&admin=\(admin)" + } + } else if let startChannel = startChannel { + if !startChannel.isEmpty { + result += "?startchannel=\(startChannel)" + } else { + result += "?startchannel" + } if let admin = admin { result += "&admin=\(admin)" } 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 { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 6845840629..a277e28ba8 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -230,6 +230,15 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } } return .startAttach(peerName, nil, choose) + } else if queryItem.name == "startgroup" || queryItem.name == "startchannel" { + var botAdminRights: ResolvedBotAdminRights? + for queryItem in queryItems { + if queryItem.name == "admin", let value = queryItem.value { + botAdminRights = ResolvedBotAdminRights(value) + break + } + } + return .peerName(peerName, .groupBotStart("", botAdminRights)) } } } diff --git a/versions.json b/versions.json index 6680658187..a3fafdebf4 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "8.9", + "app": "8.9.1", "bazel": "5.1.0", "xcode": "13.4.1" }