From e4f1c6aa72dcf765ac9813082eeee184339d72d4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 2 Sep 2022 20:10:43 +0400 Subject: [PATCH] Emoji and status improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 9 + .../Sources/ChatListController.swift | 7 +- .../ComponentFlow/Source/Base/Component.swift | 2 +- submodules/Display/Source/UIKitUtils.swift | 2 +- submodules/Postbox/Sources/PeerTable.swift | 1 + .../Sources/ReactionContextNode.swift | 3 +- submodules/TelegramApi/Sources/Api0.swift | 6 +- submodules/TelegramApi/Sources/Api19.swift | 88 ++- submodules/TelegramApi/Sources/Api20.swift | 46 ++ submodules/TelegramApi/Sources/Api23.swift | 8 +- submodules/TelegramApi/Sources/Api9.swift | 12 + .../Sources/Account/Account.swift | 2 + .../Account/AccountIntermediateState.swift | 1 + .../Sources/ApiUtils/TelegramMediaFile.swift | 2 + .../State/AccountStateManagementUtils.swift | 37 ++ .../SynchronizeSavedStickersOperation.swift | 2 +- .../SyncCore/SyncCore_Namespaces.swift | 1 + .../SyncCore/SyncCore_TelegramMediaFile.swift | 11 +- .../Stickers/CachedStickerPack.swift | 35 ++ .../Stickers/LoadedStickerPack.swift | 2 + .../Stickers/StickerSetInstallation.swift | 3 + .../DefaultDarkPresentationTheme.swift | 2 + .../DefaultDarkTintedPresentationTheme.swift | 4 + .../Sources/DefaultDayPresentationTheme.swift | 2 + .../Sources/NumericFormat.swift | 10 + .../Sources/PresentationTheme.swift | 10 + .../Sources/PresentationThemeCodable.swift | 6 + .../Sources/EmojiStatusComponent.swift | 2 +- .../EmojiStatusSelectionComponent/BUILD | 2 + .../Sources/EmojiStatusPreviewScreen.swift | 575 ++++++++++++++++++ .../EmojiStatusSelectionComponent.swift | 353 ++++++++--- .../Sources/EmojiPagerContentComponent.swift | 102 +++- .../Sources/ChatEntityKeyboardInputNode.swift | 9 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 9 +- 34 files changed, 1190 insertions(+), 176 deletions(-) create mode 100644 submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b7c5b36d85..0f87ce23d3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8013,3 +8013,12 @@ Sorry for the inconvenience."; "Premium.PricePerMonth" = "%@/month"; "Premium.PricePerYear" = "%@/year"; + +"SetTimeoutFor.Minutes_1" = "Set for 1 minute"; +"SetTimeoutFor.Minutes_any" = "Set for %@ minutes"; + +"SetTimeoutFor.Hours_1" = "Set for 1 hour"; +"SetTimeoutFor.Hours_any" = "Set for %@ hours"; + +"SetTimeoutFor.Days_1" = "Set for 1 day"; +"SetTimeoutFor.Days_any" = "Set for %@ days"; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6cda64f143..83246e2df4 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -850,6 +850,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private func openStatusSetup(sourceView: UIView) { self.emojiStatusSelectionController?.dismiss() + var selectedItems = Set() + if let peerStatus = self.titleView.title.peerStatus, case let .emoji(emojiStatus) = peerStatus { + selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)) + } let controller = EmojiStatusSelectionController( context: self.context, mode: .statusSelection, @@ -864,7 +868,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController topReactionItems: [], areUnicodeEmojiEnabled: false, areCustomEmojiEnabled: true, - chatPeerId: self.context.account.peerId + chatPeerId: self.context.account.peerId, + selectedItems: selectedItems ), destinationItemView: { [weak sourceView] in return sourceView diff --git a/submodules/ComponentFlow/Source/Base/Component.swift b/submodules/ComponentFlow/Source/Base/Component.swift index ea9a8c2bf2..71bafb2dd5 100644 --- a/submodules/ComponentFlow/Source/Base/Component.swift +++ b/submodules/ComponentFlow/Source/Base/Component.swift @@ -173,7 +173,7 @@ public class ComponentGesture { } public class AnyComponent: _TypeErasedComponent, Equatable { - fileprivate let wrapped: _TypeErasedComponent + public let wrapped: _TypeErasedComponent public init(_ component: ComponentType) where ComponentType.EnvironmentType == EnvironmentType { self.wrapped = component diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 13b36c6daa..fad53cadfd 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -487,7 +487,7 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { subtree.position = sublayer.position subtree.bounds = sublayer.bounds subtree.anchorPoint = sublayer.anchorPoint - layer.addSublayer(subtree) + view.addSublayer(subtree) } else { return nil } diff --git a/submodules/Postbox/Sources/PeerTable.swift b/submodules/Postbox/Sources/PeerTable.swift index 6f40d870d3..c515cd5a21 100644 --- a/submodules/Postbox/Sources/PeerTable.swift +++ b/submodules/Postbox/Sources/PeerTable.swift @@ -6,6 +6,7 @@ final class PeerTable: Table { } private let reverseAssociatedTable: ReverseAssociatedPeerTable + //private let peerTimeoutPropertiesTable: PeerTimeoutPropertiesTable private let sharedEncoder = PostboxEncoder() private let sharedKey = ValueBoxKey(length: 8) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index bf3db6ecf6..99ba86362b 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1129,7 +1129,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { customLayout: emojiContentLayout, externalBackground: EmojiPagerContentComponent.ExternalBackground( effectContainerView: self.backgroundNode.vibrancyEffectView?.contentView - ) + ), + useOpaqueTheme: false ) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 18d73ee266..24dfbba406 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -355,6 +355,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) } dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) } dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) } + dict[701560302] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultStatuses($0) } dict[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) } dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) } dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) } @@ -697,6 +698,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1954007928] = { return Api.SecureValueType.parse_secureValueTypeRentalAgreement($0) } dict[-368907213] = { return Api.SecureValueType.parse_secureValueTypeTemporaryRegistration($0) } dict[-63531698] = { return Api.SecureValueType.parse_secureValueTypeUtilityBill($0) } + dict[-1206095820] = { return Api.SendAsPeer.parse_sendAsPeer($0) } dict[-44119819] = { return Api.SendMessageAction.parse_sendMessageCancelAction($0) } dict[1653390447] = { return Api.SendMessageAction.parse_sendMessageChooseContactAction($0) } dict[-1336228175] = { return Api.SendMessageAction.parse_sendMessageChooseStickerAction($0) } @@ -938,7 +940,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) } dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) } dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) } - dict[-2091463255] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) } + dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) } dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) } dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) } dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) } @@ -1556,6 +1558,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.SecureValueType: _1.serialize(buffer, boxed) + case let _1 as Api.SendAsPeer: + _1.serialize(buffer, boxed) case let _1 as Api.SendMessageAction: _1.serialize(buffer, boxed) case let _1 as Api.ShippingOption: diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 7e508b2e6f..9ce012be55 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -1,3 +1,45 @@ +public extension Api { + enum SendAsPeer: TypeConstructorDescription { + case sendAsPeer(flags: Int32, peer: Api.Peer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendAsPeer(let flags, let peer): + if boxed { + buffer.appendInt32(-1206095820) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sendAsPeer(let flags, let peer): + return ("sendAsPeer", [("flags", String(describing: flags)), ("peer", String(describing: peer))]) + } + } + + public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum SendMessageAction: TypeConstructorDescription { case sendMessageCancelAction @@ -842,49 +884,3 @@ public extension Api { } } -public extension Api { - enum StickerPack: TypeConstructorDescription { - case stickerPack(emoticon: String, documents: [Int64]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerPack(let emoticon, let documents): - if boxed { - buffer.appendInt32(313694676) - } - serializeString(emoticon, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickerPack(let emoticon, let documents): - return ("stickerPack", [("emoticon", String(describing: emoticon)), ("documents", String(describing: documents))]) - } - } - - public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { - var _1: String? - _1 = parseString(reader) - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index a01dd5ab38..fb4e9a17f9 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -1,3 +1,49 @@ +public extension Api { + enum StickerPack: TypeConstructorDescription { + case stickerPack(emoticon: String, documents: [Int64]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerPack(let emoticon, let documents): + if boxed { + buffer.appendInt32(313694676) + } + serializeString(emoticon, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerPack(let emoticon, let documents): + return ("stickerPack", [("emoticon", String(describing: emoticon)), ("documents", String(describing: documents))]) + } + } + + public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { + var _1: String? + _1 = parseString(reader) + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum StickerSet: TypeConstructorDescription { case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32) diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index a7f212ad75..ec423f45e7 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -692,13 +692,13 @@ public extension Api.channels { } public extension Api.channels { enum SendAsPeers: TypeConstructorDescription { - case sendAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + case sendAsPeers(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .sendAsPeers(let peers, let chats, let users): if boxed { - buffer.appendInt32(-2091463255) + buffer.appendInt32(-191450938) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(peers.count)) @@ -727,9 +727,9 @@ public extension Api.channels { } public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? { - var _1: [Api.Peer]? + var _1: [Api.SendAsPeer]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self) } var _2: [Api.Chat]? if let _ = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index 80c05e5040..df49a053d9 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -677,6 +677,7 @@ public extension Api { case inputStickerSetAnimatedEmoji case inputStickerSetAnimatedEmojiAnimations case inputStickerSetDice(emoticon: String) + case inputStickerSetEmojiDefaultStatuses case inputStickerSetEmojiGenericAnimations case inputStickerSetEmpty case inputStickerSetID(id: Int64, accessHash: Int64) @@ -702,6 +703,12 @@ public extension Api { buffer.appendInt32(-427863538) } serializeString(emoticon, buffer: buffer, boxed: false) + break + case .inputStickerSetEmojiDefaultStatuses: + if boxed { + buffer.appendInt32(701560302) + } + break case .inputStickerSetEmojiGenericAnimations: if boxed { @@ -745,6 +752,8 @@ public extension Api { return ("inputStickerSetAnimatedEmojiAnimations", []) case .inputStickerSetDice(let emoticon): return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))]) + case .inputStickerSetEmojiDefaultStatuses: + return ("inputStickerSetEmojiDefaultStatuses", []) case .inputStickerSetEmojiGenericAnimations: return ("inputStickerSetEmojiGenericAnimations", []) case .inputStickerSetEmpty: @@ -775,6 +784,9 @@ public extension Api { return nil } } + public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses + } public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index fa693e3162..e36c06bd08 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1182,6 +1182,8 @@ public class Account { self.managedOperationsDisposable.add(managedRecentReactions(postbox: self.postbox, network: self.network).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(self.managedTopReactionsDisposable) + + self.managedOperationsDisposable.add(_internal_loadedStickerPack(postbox: self.postbox, network: self.network, reference: .iconStatusEmoji, forceActualized: true).start()) } if !supplementary { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 09af26f240..03a63afb33 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -45,6 +45,7 @@ enum AccountStateUpdatePinnedItemIdsOperation { enum AccountStateUpdateStickerPacksOperation { case add(Api.messages.StickerSet) case reorder(SynchronizeInstalledStickerPacksOperationNamespace, [Int64]) + case reorderToTop(SynchronizeInstalledStickerPacksOperationNamespace, [Int64]) case sync } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 04ece7acc7..9ec4de16b0 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -66,6 +66,8 @@ extension StickerPackReference { self = .premiumGifts case .inputStickerSetEmojiGenericAnimations: self = .emojiGenericAnimations + case .inputStickerSetEmojiDefaultStatuses: + self = .iconStatusEmoji } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 29c870a805..eb207f3aac 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1392,6 +1392,16 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo namespace = .stickers } updatedState.addUpdateInstalledStickerPacks(.reorder(namespace, order)) + case let .updateMoveStickerSetToTop(flags, stickerset): + let namespace: SynchronizeInstalledStickerPacksOperationNamespace + if (flags & (1 << 0)) != 0 { + namespace = .masks + } else if (flags & (1 << 1)) != 0 { + namespace = .emoji + } else { + namespace = .stickers + } + updatedState.addUpdateInstalledStickerPacks(.reorderToTop(namespace, [stickerset])) case .updateStickerSets: updatedState.addUpdateInstalledStickerPacks(.sync) case .updateSavedGifs: @@ -3666,6 +3676,33 @@ func replayFinalState( } transaction.replaceItemCollectionInfos(namespace: collectionNamespace, itemCollectionInfos: updatedInfos.map { ($0.id, $0) }) } + case let .reorderToTop(namespace, ids): + let collectionNamespace: ItemCollectionId.Namespace + switch namespace { + case .stickers: + collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks + case .masks: + collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks + case .emoji: + collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks + } + let currentInfos = transaction.getItemCollectionsInfos(namespace: collectionNamespace).map { $0.1 as! StickerPackCollectionInfo } + + var currentDict: [ItemCollectionId: StickerPackCollectionInfo] = [:] + for info in currentInfos { + currentDict[info.id] = info + } + var updatedInfos: [StickerPackCollectionInfo] = [] + for id in ids { + let currentInfo = currentDict[ItemCollectionId(namespace: collectionNamespace, id: id)]! + updatedInfos.append(currentInfo) + } + for info in currentInfos { + if !updatedInfos.contains(where: { $0.id == info.id }) { + updatedInfos.append(info) + } + } + transaction.replaceItemCollectionInfos(namespace: collectionNamespace, itemCollectionInfos: updatedInfos.map { ($0.id, $0) }) case .sync: syncStickers = true syncMasks = true diff --git a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift index df43c43664..bb449b1dc3 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift @@ -61,7 +61,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe if !found { fetchReference = packReference } - case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations: + case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations, .iconStatusEmoji: break } if let fetchReference = fetchReference { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 60fa94d66c..97fa03c16b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -49,6 +49,7 @@ public struct Namespaces { public static let CloudPremiumGifts: Int32 = 7 public static let CloudEmojiPacks: Int32 = 8 public static let CloudEmojiGenericAnimations: Int32 = 9 + public static let CloudIconStatusEmoji: Int32 = 10 } public struct OrderedItemList { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index df92af2fa0..4cc5a1e77c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -21,6 +21,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { case animatedEmojiAnimations case premiumGifts case emojiGenericAnimations + case iconStatusEmoji public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { @@ -83,7 +84,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { encoder.encodeInt32(4, forKey: "r") case .premiumGifts: encoder.encodeInt32(5, forKey: "r") - case .emojiGenericAnimations: + case .emojiGenericAnimations, .iconStatusEmoji: preconditionFailure() } } @@ -108,7 +109,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { try container.encode(4 as Int32, forKey: "r") case .premiumGifts: try container.encode(5 as Int32, forKey: "r") - case .emojiGenericAnimations: + case .emojiGenericAnimations, .iconStatusEmoji: preconditionFailure() } } @@ -157,6 +158,12 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { } else { return false } + case .iconStatusEmoji: + if case .iconStatusEmoji = rhs { + return true + } else { + return false + } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift index 84153830eb..991f9b9abd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift @@ -30,7 +30,16 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, case let .dice(emoji): namespace = Namespaces.ItemCollection.CloudDice id = Int64(murMurHashString32(emoji)) + case .emojiGenericAnimations: + namespace = Namespaces.ItemCollection.CloudEmojiGenericAnimations + id = 0 + case .iconStatusEmoji: + namespace = Namespaces.ItemCollection.CloudIconStatusEmoji + id = 0 + case .id: + break default: + assertionFailure() break } if let namespace = namespace, let id = id { @@ -152,6 +161,20 @@ func _internal_cachedStickerPack(postbox: Postbox, network: Network, reference: } else { return (.fetching, true, nil) } + case .iconStatusEmoji: + let namespace = Namespaces.ItemCollection.CloudIconStatusEmoji + let id: ItemCollectionId.Id = 0 + if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { + previousHash = cached.hash + let current: CachedStickerPackResult = .result(info, cached.items, false) + if cached.hash != info.hash { + return (current, true, previousHash) + } else { + return (current, false, previousHash) + } + } else { + return (.fetching, true, nil) + } } } |> mapToSignal { result, loadRemote, previousHash in @@ -272,6 +295,18 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { return (info, cached.items, false) } + case .iconStatusEmoji: + let namespace = Namespaces.ItemCollection.CloudIconStatusEmoji + let id: ItemCollectionId.Id = 0 + if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo { + let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id)) + if !items.isEmpty { + return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true) + } + } + if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { + return (info, cached.items, false) + } } return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift index 269643129e..a4f44658be 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift @@ -24,6 +24,8 @@ extension StickerPackReference { return .inputStickerSetPremiumGifts case .emojiGenericAnimations: return .inputStickerSetEmojiGenericAnimations + case .iconStatusEmoji: + return .inputStickerSetEmojiDefaultStatuses } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift index 7246c96425..04330c7e02 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift @@ -49,6 +49,9 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference: case .emojiGenericAnimations: collectionId = nil input = .inputStickerSetEmojiGenericAnimations + case .iconStatusEmoji: + collectionId = nil + input = .inputStickerSetEmojiDefaultStatuses } let localSignal: (ItemCollectionId) -> Signal<(ItemCollectionInfo, [ItemCollectionItem])?, NoError> = { collectionId in diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 7e492f3f33..747d04220b 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -612,6 +612,8 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati panelContentVibrantOverlayColor: UIColor(rgb: 0x808080), panelContentControlVibrantOverlayColor: UIColor(rgb: 0x808080).mixedWith(UIColor(rgb: 0x000000), alpha: 0.35), panelContentControlVibrantSelectionColor: UIColor(white: 1.0, alpha: 0.1), + panelContentControlOpaqueOverlayColor: UIColor(white: 1.0, alpha: 0.1), + panelContentControlOpaqueSelectionColor: UIColor(white: 1.0, alpha: 0.1), stickersBackgroundColor: UIColor(rgb: 0x000000), stickersSectionTextColor: UIColor(rgb: 0x7b7b7b), stickersSearchBackgroundColor: UIColor(rgb: 0x1c1c1d), diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index c7b4d2efe5..828697c12e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -443,6 +443,8 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme panelHighlightedIconColor: mainSecondaryTextColor?.withAlphaComponent(0.5).mixedWith(chat.inputPanel.primaryTextColor, alpha: 0.35), panelContentVibrantOverlayColor: mainSecondaryTextColor?.withAlphaComponent(0.5), panelContentControlVibrantOverlayColor: mainSecondaryTextColor?.withAlphaComponent(0.3), + panelContentControlOpaqueOverlayColor: mainSecondaryTextColor?.withAlphaComponent(0.3), + panelContentControlOpaqueSelectionColor: mainSecondaryTextColor?.withAlphaComponent(0.3), stickersBackgroundColor: additionalBackgroundColor, stickersSectionTextColor: mainSecondaryTextColor?.withAlphaComponent(0.5), stickersSearchBackgroundColor: accentColor?.withMultiplied(hue: 1.009, saturation: 0.621, brightness: 0.15), @@ -845,6 +847,8 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres panelContentVibrantOverlayColor: mainSecondaryTextColor.withAlphaComponent(0.5), panelContentControlVibrantOverlayColor: mainSecondaryTextColor.withAlphaComponent(0.3), panelContentControlVibrantSelectionColor: mainSecondaryTextColor.withAlphaComponent(0.1), + panelContentControlOpaqueOverlayColor: mainSecondaryTextColor.withAlphaComponent(0.1), + panelContentControlOpaqueSelectionColor: mainSecondaryTextColor.withAlphaComponent(0.1), stickersBackgroundColor: additionalBackgroundColor, stickersSectionTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), stickersSearchBackgroundColor: accentColor.withMultiplied(hue: 1.009, saturation: 0.621, brightness: 0.15), diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index aa32b3e3e2..49e437b3e3 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -866,6 +866,8 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio panelContentVibrantOverlayColor: UIColor(white: 0.7, alpha: 0.65), panelContentControlVibrantOverlayColor: UIColor(white: 0.85, alpha: 0.65), panelContentControlVibrantSelectionColor: UIColor(white: 0.85, alpha: 0.1), + panelContentControlOpaqueOverlayColor: UIColor(white: 0.0, alpha: 0.3), + panelContentControlOpaqueSelectionColor: UIColor(white: 0.0, alpha: 0.1), stickersBackgroundColor: UIColor(rgb: 0xe8ebf0), stickersSectionTextColor: UIColor(rgb: 0x9099a2), stickersSearchBackgroundColor: UIColor(rgb: 0xd9dbe1), diff --git a/submodules/TelegramPresentationData/Sources/NumericFormat.swift b/submodules/TelegramPresentationData/Sources/NumericFormat.swift index 395061337b..8ba787641f 100644 --- a/submodules/TelegramPresentationData/Sources/NumericFormat.swift +++ b/submodules/TelegramPresentationData/Sources/NumericFormat.swift @@ -146,6 +146,16 @@ public func muteForIntervalString(strings: PresentationStrings, value: Int32) -> } } +public func setTimeoutForIntervalString(strings: PresentationStrings, value: Int32) -> String { + if value < 60 * 60 { + return strings.SetTimeoutFor_Minutes(max(1, value / (60))) + } else if value < 60 * 60 * 24 { + return strings.SetTimeoutFor_Hours(max(1, value / (60 * 60))) + } else { + return strings.SetTimeoutFor_Days(max(1, value / (60 * 60 * 24))) + } +} + public func mutedForTimeIntervalString(strings: PresentationStrings, value: Int32) -> String { if value < 60 * 60 { return strings.MutedForTime_Minutes(max(1, value / (60))) diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index 15d2a62989..ad4e462533 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -1146,6 +1146,8 @@ public final class PresentationThemeInputMediaPanel { public let panelContentVibrantOverlayColor: UIColor public let panelContentControlVibrantOverlayColor: UIColor public let panelContentControlVibrantSelectionColor: UIColor + public let panelContentControlOpaqueOverlayColor: UIColor + public let panelContentControlOpaqueSelectionColor: UIColor public let stickersBackgroundColor: UIColor public let stickersSectionTextColor: UIColor public let stickersSearchBackgroundColor: UIColor @@ -1163,6 +1165,8 @@ public final class PresentationThemeInputMediaPanel { panelContentVibrantOverlayColor: UIColor, panelContentControlVibrantOverlayColor: UIColor, panelContentControlVibrantSelectionColor: UIColor, + panelContentControlOpaqueOverlayColor: UIColor, + panelContentControlOpaqueSelectionColor: UIColor, stickersBackgroundColor: UIColor, stickersSectionTextColor: UIColor, stickersSearchBackgroundColor: UIColor, @@ -1179,6 +1183,8 @@ public final class PresentationThemeInputMediaPanel { self.panelContentVibrantOverlayColor = panelContentVibrantOverlayColor self.panelContentControlVibrantOverlayColor = panelContentControlVibrantOverlayColor self.panelContentControlVibrantSelectionColor = panelContentControlVibrantSelectionColor + self.panelContentControlOpaqueOverlayColor = panelContentControlOpaqueOverlayColor + self.panelContentControlOpaqueSelectionColor = panelContentControlOpaqueSelectionColor self.stickersBackgroundColor = stickersBackgroundColor self.stickersSectionTextColor = stickersSectionTextColor self.stickersSearchBackgroundColor = stickersSearchBackgroundColor @@ -1197,6 +1203,8 @@ public final class PresentationThemeInputMediaPanel { panelContentVibrantOverlayColor: UIColor? = nil, panelContentControlVibrantOverlayColor: UIColor? = nil, panelContentControlVibrantSelectionColor: UIColor? = nil, + panelContentControlOpaqueOverlayColor: UIColor? = nil, + panelContentControlOpaqueSelectionColor: UIColor? = nil, stickersBackgroundColor: UIColor? = nil, stickersSectionTextColor: UIColor? = nil, stickersSearchBackgroundColor: UIColor? = nil, @@ -1214,6 +1222,8 @@ public final class PresentationThemeInputMediaPanel { panelContentVibrantOverlayColor: panelContentVibrantOverlayColor ?? self.panelContentVibrantOverlayColor, panelContentControlVibrantOverlayColor: panelContentControlVibrantOverlayColor ?? self.panelContentControlVibrantOverlayColor, panelContentControlVibrantSelectionColor: panelContentControlVibrantSelectionColor ?? self.panelContentControlVibrantSelectionColor, + panelContentControlOpaqueOverlayColor: panelContentControlOpaqueOverlayColor ?? self.panelContentControlOpaqueOverlayColor, + panelContentControlOpaqueSelectionColor: panelContentControlOpaqueSelectionColor ?? self.panelContentControlOpaqueSelectionColor, stickersBackgroundColor: stickersBackgroundColor ?? self.stickersBackgroundColor, stickersSectionTextColor: stickersSectionTextColor ?? self.stickersSectionTextColor, stickersSearchBackgroundColor: stickersSearchBackgroundColor ?? self.stickersSearchBackgroundColor, diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 03f79c6d4b..f96f4dd60b 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -1609,6 +1609,8 @@ extension PresentationThemeInputMediaPanel: Codable { case panelContentVibrantOverlay case panelContentControlVibrantOverlay case panelContentControlVibrantSelection + case panelContentControlOpaqueOverlay + case panelContentControlOpaqueSelection case stickersBg case stickersSectionText case stickersSearchBg @@ -1648,6 +1650,8 @@ extension PresentationThemeInputMediaPanel: Codable { panelContentVibrantOverlayColor: try decodeColor(values, .panelContentVibrantOverlay, fallbackKey: "\(codingPath).stickersSectionText"), panelContentControlVibrantOverlayColor: try decodeColor(values, .panelContentControlVibrantOverlay, fallbackKey: "\(codingPath).stickersSectionText"), panelContentControlVibrantSelectionColor: try decodeColor(values, .panelContentControlVibrantSelection, fallbackKey: "\(codingPath).stickersSectionText"), + panelContentControlOpaqueOverlayColor: try decodeColor(values, .panelContentControlOpaqueOverlay, fallbackKey: "\(codingPath).stickersSectionText"), + panelContentControlOpaqueSelectionColor: try decodeColor(values, .panelContentControlOpaqueSelection, fallbackKey: "\(codingPath).stickersSectionText"), stickersBackgroundColor: try decodeColor(values, .stickersBg), stickersSectionTextColor: try decodeColor(values, .stickersSectionText), stickersSearchBackgroundColor: try decodeColor(values, .stickersSearchBg), @@ -1666,6 +1670,8 @@ extension PresentationThemeInputMediaPanel: Codable { try encodeColor(&values, self.panelContentVibrantOverlayColor, .panelContentVibrantOverlay) try encodeColor(&values, self.panelContentControlVibrantOverlayColor, .panelContentControlVibrantOverlay) try encodeColor(&values, self.panelContentControlVibrantSelectionColor, .panelContentControlVibrantSelection) + try encodeColor(&values, self.panelContentControlOpaqueOverlayColor, .panelContentControlOpaqueOverlay) + try encodeColor(&values, self.panelContentControlOpaqueSelectionColor, .panelContentControlOpaqueSelection) try encodeColor(&values, self.stickersBackgroundColor, .stickersBg) try encodeColor(&values, self.stickersSectionTextColor, .stickersSectionText) try encodeColor(&values, self.stickersSearchBackgroundColor, .stickersSearchBg) diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index cfdf639c33..1804e32118 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -390,7 +390,7 @@ public final class EmojiStatusComponent: Component { if case let .CustomEmoji(_, _, packReference) = attribute { switch packReference { case let .id(id, _): - if id == 773947703670341676 { + if id == 773947703670341676 || id == 2964141614563343 { accentTint = true } default: diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD index 85425290f3..2bb7cef7a4 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD @@ -20,8 +20,10 @@ swift_library( "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent", "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", "//submodules/Components/PagerComponent:PagerComponent", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/lottie-ios:Lottie", diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift new file mode 100644 index 0000000000..9d3138ecc0 --- /dev/null +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift @@ -0,0 +1,575 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import ComponentFlow +import TelegramPresentationData +import AccountContext +import ComponentDisplayAdapters +import MultilineTextComponent +import EmojiStatusComponent + +protocol ContextMenuItemWithAction: AnyObject { + func performAction() +} + +private final class ContextMenuActionItem: Component, ContextMenuItemWithAction { + typealias EnvironmentType = ContextMenuActionItemEnvironment + + let title: String + let action: () -> Void + + init(title: String, action: @escaping () -> Void) { + self.title = title + self.action = action + } + + static func ==(lhs: ContextMenuActionItem, rhs: ContextMenuActionItem) -> Bool { + if lhs.title != rhs.title { + return false + } + return true + } + + func performAction() { + self.action() + } + + final class View: UIView { + private let titleView: ComponentView + + override init(frame: CGRect) { + self.titleView = ComponentView() + + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ContextMenuActionItem, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let contextEnvironment = environment[EnvironmentType.self].value + + let sideInset: CGFloat = 16.0 + let height: CGFloat = 44.0 + + let titleSize = self.titleView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: contextEnvironment.theme.contextMenu.primaryColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + if let view = self.titleView.view { + if view.superview == nil { + self.addSubview(view) + } + transition.setFrame(view: view, frame: titleFrame) + } + + return CGSize(width: sideInset * 2.0 + titleSize.width, height: height) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ContextMenuActionItemEnvironment: Equatable { + let theme: PresentationTheme + + init( + theme: PresentationTheme + ) { + self.theme = theme + } + + static func ==(lhs: ContextMenuActionItemEnvironment, rhs: ContextMenuActionItemEnvironment) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + return true + } +} + +private final class ContextMenuActionsComponent: Component { + let theme: PresentationTheme + let items: [AnyComponentWithIdentity] + + init( + theme: PresentationTheme, + items: [AnyComponentWithIdentity] + ) { + self.theme = theme + self.items = items + } + + static func ==(lhs: ContextMenuActionsComponent, rhs: ContextMenuActionsComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + final class View: UIButton { + private final class ItemView { + let view = ComponentView() + let separatorView = UIView() + } + + private let backgroundView: BlurredBackgroundView + private var itemViews: [AnyHashable: ItemView] = [:] + private var highligntedBackgroundView: UIView? + + private var component: ContextMenuActionsComponent? + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + + super.init(frame: frame) + + self.clipsToBounds = true + self.layer.cornerRadius = 14.0 + + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.setHighlightedItem(id: self.itemAtPoint(point: touch.location(in: self))) + + return super.beginTracking(touch, with: event) + } + + override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.setHighlightedItem(id: self.itemAtPoint(point: touch.location(in: self))) + + return super.continueTracking(touch, with: event) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + if let component = self.component, let touch = touch, let id = self.itemAtPoint(point: touch.location(in: self)) { + self.setHighlightedItem(id: id) + for item in component.items { + if item.id == id { + if let itemComponent = item.component.wrapped as? ContextMenuItemWithAction { + itemComponent.performAction() + } + break + } + } + } else { + self.setHighlightedItem(id: nil) + } + + super.endTracking(touch, with: event) + } + + override func cancelTracking(with event: UIEvent?) { + self.setHighlightedItem(id: nil) + + super.cancelTracking(with: event) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + self.setHighlightedItem(id: nil) + + super.touchesCancelled(touches, with: event) + } + + private func itemAtPoint(point: CGPoint) -> AnyHashable? { + for (id, itemView) in self.itemViews { + guard let itemComponentView = itemView.view.view else { + continue + } + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemComponentView.frame.minY), size: CGSize(width: self.bounds.width, height: itemComponentView.bounds.height)) + if itemFrame.contains(point) { + return id + } + } + return nil + } + + private func setHighlightedItem(id: AnyHashable?) { + if let component = self.component, let id = id, let itemView = self.itemViews[id], let itemComponentView = itemView.view.view { + let highligntedBackgroundView: UIView + if let current = self.highligntedBackgroundView { + highligntedBackgroundView = current + } else { + highligntedBackgroundView = UIView() + self.highligntedBackgroundView = highligntedBackgroundView + var found = false + outer: for subview in self.subviews { + for (_, listItemView) in self.itemViews { + if subview === listItemView.view.view { + self.insertSubview(highligntedBackgroundView, belowSubview: subview) + found = true + break outer + } + } + } + if !found { + self.insertSubview(highligntedBackgroundView, aboveSubview: self.backgroundView) + } + + highligntedBackgroundView.backgroundColor = component.theme.contextMenu.itemHighlightedBackgroundColor + } + var highlightFrame = CGRect(origin: CGPoint(x: 0.0, y: itemComponentView.frame.minY), size: CGSize(width: self.bounds.width, height: itemComponentView.bounds.height)) + if id != component.items.last?.id { + highlightFrame.size.height += UIScreenPixel + } + + highligntedBackgroundView.frame = highlightFrame + } else { + if let highligntedBackgroundView = self.highligntedBackgroundView { + self.highligntedBackgroundView = nil + highligntedBackgroundView.removeFromSuperview() + } + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + return self + } + + func update(component: ContextMenuActionsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let availableItemSize = availableSize + + var itemsSize = CGSize() + var validIds = Set() + var currentItems: [(id: AnyHashable, itemFrame: CGRect, itemTransition: Transition)] = [] + for i in 0 ..< component.items.count { + let item = component.items[i] + validIds.insert(item.id) + + let itemView: ItemView + var itemTransition = transition + if let current = self.itemViews[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ItemView() + self.itemViews[item.id] = itemView + self.insertSubview(itemView.separatorView, aboveSubview: self.backgroundView) + } + + let itemSize = itemView.view.update( + transition: itemTransition, + component: item.component, + environment: { + ContextMenuActionItemEnvironment(theme: component.theme) + }, + containerSize: availableItemSize + ) + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsSize.height), size: itemSize) + if let view = itemView.view.view { + if view.superview == nil { + self.addSubview(view) + } + itemTransition.setFrame(view: view, frame: itemFrame) + } + currentItems.append((item.id, itemFrame, itemTransition)) + itemsSize.width = max(itemsSize.width, itemSize.width) + itemsSize.height += itemSize.height + } + + itemsSize.width = max(itemsSize.width, 180.0) + + for i in 0 ..< currentItems.count { + let item = currentItems[i] + guard let itemView = self.itemViews[item.id] else { + continue + } + itemView.separatorView.backgroundColor = component.theme.contextMenu.itemSeparatorColor + itemView.separatorView.isHidden = i == currentItems.count - 1 + item.itemTransition.setFrame(view: itemView.separatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: item.itemFrame.maxY), size: CGSize(width: itemsSize.width, height: UIScreenPixel))) + } + + var removeIds: [AnyHashable] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removeIds.append(id) + itemView.view.view?.removeFromSuperview() + itemView.separatorView.removeFromSuperview() + } + } + + self.backgroundView.updateColor(color: component.theme.contextMenu.backgroundColor, transition: .immediate) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: itemsSize)) + self.backgroundView.update(size: itemsSize, transition: transition.containedViewLayoutTransition) + + return itemsSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class EmojiStatusPreviewScreenComponent: Component { + struct StatusResult { + let timeout: Int + let sourceView: UIView + } + + final class TransitionAnimation { + enum TransitionType { + case animateIn(sourceLayer: CALayer) + } + + let transitionType: TransitionType + + init(transitionType: TransitionType) { + self.transitionType = transitionType + } + } + + typealias EnvironmentType = Empty + + let theme: PresentationTheme + let strings: PresentationStrings + let item: EmojiStatusComponent + let dismiss: (StatusResult?) -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + item: EmojiStatusComponent, + dismiss: @escaping (StatusResult?) -> Void + ) { + self.theme = theme + self.strings = strings + self.item = item + self.dismiss = dismiss + } + + static func ==(lhs: EmojiStatusPreviewScreenComponent, rhs: EmojiStatusPreviewScreenComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.item != rhs.item { + return false + } + return true + } + + final class View: UIView { + private let backgroundView: BlurredBackgroundView + private let itemView: ComponentView + private let actionsView: ComponentView + + private var component: EmojiStatusPreviewScreenComponent? + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + self.itemView = ComponentView() + self.actionsView = ComponentView() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.backgroundTapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func backgroundTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.component?.dismiss(nil) + } + } + + func update(component: EmojiStatusPreviewScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let itemSpacing: CGFloat = 12.0 + + let itemSize = self.itemView.update( + transition: transition, + component: AnyComponent(component.item), + environment: {}, + containerSize: CGSize(width: 128.0, height: 128.0) + ) + + var menuItems: [AnyComponentWithIdentity] = [] + let delayDurations: [Int] = [ + 1 * 60 * 60, + 2 * 60 * 60, + 8 * 60 * 60, + 2 * 24 * 60 * 60 + ] + for duration in delayDurations { + menuItems.append(AnyComponentWithIdentity(id: duration, component: AnyComponent(ContextMenuActionItem( + title: setTimeoutForIntervalString(strings: component.strings, value: Int32(duration)), + action: { [weak self] in + guard let strongSelf = self, let component = strongSelf.component else { + return + } + guard let itemComponentView = strongSelf.itemView.view else { + return + } + component.dismiss(StatusResult(timeout: duration, sourceView: itemComponentView)) + } + )))) + } + let actionsSize = self.actionsView.update( + transition: transition, + component: AnyComponent(ContextMenuActionsComponent( + theme: component.theme, + items: menuItems + )), + environment: {}, + containerSize: availableSize + ) + + let totalContentHeight = itemSize.height + itemSpacing + actionsSize.height + + let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((availableSize.height - totalContentHeight) / 2.0)), size: CGSize(width: availableSize.width, height: totalContentHeight)) + + let itemFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.width - itemSize.width) / 2.0), y: contentFrame.minY), size: itemSize) + let actionsFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - actionsSize.width) / 2.0), y: itemFrame.maxY + itemSpacing), size: actionsSize) + + if let itemComponentView = self.itemView.view { + if itemComponentView.superview == nil { + self.addSubview(itemComponentView) + } + transition.setFrame(view: itemComponentView, frame: itemFrame) + } + + if let actionsComponentView = self.actionsView.view { + if actionsComponentView.superview == nil { + self.addSubview(actionsComponentView) + } + transition.setFrame(view: actionsComponentView, frame: actionsFrame) + } + + self.backgroundView.updateColor(color: component.theme.contextMenu.dimColor, transition: .immediate) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.backgroundView.update(size: availableSize, transition: transition.containedViewLayoutTransition) + + if let transitionAnimation = transition.userData(TransitionAnimation.self) { + switch transitionAnimation.transitionType { + case let .animateIn(sourceLayer): + var additionalPositionDifference = CGPoint() + if let copyLayer = sourceLayer.snapshotContentTree(), let itemComponentView = self.itemView.view { + sourceLayer.isHidden = true + copyLayer.frame = sourceLayer.convert(sourceLayer.bounds, to: self.layer) + self.layer.addSublayer(copyLayer) + + copyLayer.animatePosition(from: copyLayer.frame.center, to: itemComponentView.frame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + copyLayer.animateScale(from: 1.0, to: itemComponentView.bounds.width / copyLayer.bounds.width, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + copyLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyLayer] _ in + copyLayer?.removeFromSuperlayer() + }) + + additionalPositionDifference = CGPoint(x: itemComponentView.frame.center.x - copyLayer.frame.center.x, y: itemComponentView.frame.center.y - copyLayer.frame.center.y) + itemComponentView.layer.animatePosition(from: copyLayer.frame.center, to: itemComponentView.frame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + itemComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) + itemComponentView.layer.animateScale(from: copyLayer.bounds.width / itemComponentView.bounds.width, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } + + self.backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + if let actionsComponentView = self.actionsView.view { + actionsComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + actionsComponentView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6) + actionsComponentView.layer.animateSpring(from: (-actionsComponentView.bounds.height / 2.0) as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.translation.y", duration: 0.6) + + let _ = additionalPositionDifference + //actionsComponentView.layer.animatePosition(from: CGPoint(x: -additionalPositionDifference.x, y: -additionalPositionDifference.y), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + } + } + + return availableSize + } + + func animateOut(targetLayer: CALayer?, completion: @escaping () -> Void) { + if let targetLayer = targetLayer, let itemComponentView = self.itemView.view { + targetLayer.isHidden = false + let targetLayerPosition = targetLayer.position + let targetLayerSuperlayer = targetLayer.superlayer + var targetLayerIndexPosition: UInt32? + if let targetLayerSuperlayer = targetLayerSuperlayer { + if let index = targetLayerSuperlayer.sublayers?.firstIndex(of: targetLayer) { + targetLayerIndexPosition = UInt32(index) + } + } + + let localTargetPosition = targetLayer.convert(targetLayer.bounds.center, to: self.layer) + self.layer.addSublayer(targetLayer) + targetLayer.position = localTargetPosition + + targetLayer.animatePosition(from: itemComponentView.frame.center, to: localTargetPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + targetLayer.animateScale(from: itemComponentView.bounds.width / targetLayer.bounds.width, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak targetLayer, weak targetLayerSuperlayer] _ in + if let targetLayer = targetLayer, let targetLayerSuperlayer = targetLayerSuperlayer { + if let targetLayerIndexPosition = targetLayerIndexPosition { + targetLayerSuperlayer.insertSublayer(targetLayer, at: targetLayerIndexPosition) + targetLayer.position = targetLayerPosition + } + } + completion() + }) + targetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) + + itemComponentView.layer.animatePosition(from: itemComponentView.frame.center, to: localTargetPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + itemComponentView.layer.animateScale(from: 1.0, to: targetLayer.bounds.width / itemComponentView.bounds.width, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + itemComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + + self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + if let actionsComponentView = self.actionsView.view { + actionsComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + }) + } + } else { + self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + if let actionsComponentView = self.actionsView.view { + actionsComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completion() + }) + } else { + completion() + } + } + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 5b4c4b7549..903282511d 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -18,6 +18,7 @@ import EmojiTextAttachmentView import TextFormat import AppBundle import GZip +import EmojiStatusComponent private func randomGenericReactionEffect(context: AccountContext) -> Signal { return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false) @@ -29,7 +30,6 @@ private func randomGenericReactionEffect(context: AccountContext) -> Signal filter { $0 != nil } |> take(1) |> mapToSignal { items -> Signal in guard let items = items else { @@ -227,6 +227,10 @@ public final class EmojiStatusSelectionController: ViewController { private var freezeUpdates: Bool = false private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? + private var previewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)? + private var dismissedPreviewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)? + private var previewScreenView: ComponentView? + private var availableReactions: AvailableReactions? private var availableReactionsDisposable: Disposable? @@ -289,11 +293,11 @@ public final class EmojiStatusSelectionController: ViewController { } emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { groupId, item, _, _, _, _ in + performItemAction: { groupId, item, _, _, _, isPreview in guard let strongSelf = self else { return } - strongSelf.applyItem(groupId: groupId, item: item) + strongSelf.applyItem(groupId: groupId, item: item, isPreview: isPreview) }, deleteBackwards: { }, @@ -340,7 +344,8 @@ public final class EmojiStatusSelectionController: ViewController { chatPeerId: nil, peekBehavior: nil, customLayout: nil, - externalBackground: nil + externalBackground: nil, + useOpaqueTheme: true ) strongSelf.refreshLayout(transition: .immediate) @@ -374,29 +379,26 @@ public final class EmojiStatusSelectionController: ViewController { self.containerLayoutUpdated(layout: layout, transition: transition) } - func animateOut(completion: @escaping () -> Void) { + func animateOut(completion: @escaping () -> Void, fromBackground: Bool) { if self.isAnimatingOut { return } self.isAnimatingOut = true - self.componentShadowLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + let duration: Double = fromBackground ? 0.1 : 0.25 + + self.componentShadowLayer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) + self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { _ in completion() }) - self.cloudLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.cloudShadowLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.cloudLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.cloudLayer0.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) + self.cloudShadowLayer0.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) + self.cloudLayer1.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) + self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) } - func animateOutToStatus(groupId: AnyHashable, item: EmojiPagerContentComponent.Item, customEffectFile: String?, destinationView: UIView) { - guard let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem( groupId: groupId, item: item) else { - self.controller?.dismiss() - return - } - + func animateOutToStatus(item: EmojiPagerContentComponent.Item, sourceLayer: CALayer, customEffectFile: String?, destinationView: UIView, fromBackground: Bool) { self.isUserInteractionEnabled = false destinationView.isHidden = true @@ -435,7 +437,7 @@ public final class EmojiStatusSelectionController: ViewController { if case let .CustomEmoji(_, _, packReference) = attribute { switch packReference { case let .id(id, _): - if id == 773947703670341676 { + if id == 773947703670341676 || id == 2964141614563343 { useCleanEffect = true } default: @@ -517,7 +519,7 @@ public final class EmojiStatusSelectionController: ViewController { var midPointY: CGFloat = localDestinationFrame.center.y - 30.0 if let layout = self.validLayout { if midPointY < layout.safeInsets.top + 8.0 { - midPointY = max(localDestinationFrame.center.y, layout.safeInsets.top + 8.0) + midPointY = max(localDestinationFrame.center.y, layout.safeInsets.top + 20.0) } } @@ -561,10 +563,24 @@ public final class EmojiStatusSelectionController: ViewController { destinationView.isHidden = false } + if let previewScreenView = self.previewScreenView { + self.previewItem = nil + self.dismissedPreviewItem = nil + self.previewScreenView = nil + + if let previewScreenComponentView = previewScreenView.view as? EmojiStatusPreviewScreenComponent.View { + previewScreenComponentView.animateOut(targetLayer: nil, completion: { [weak previewScreenComponentView] in + previewScreenComponentView?.removeFromSuperview() + }) + } else { + previewScreenView.view?.removeFromSuperview() + } + } + self.animateOut(completion: { contentCompleted = true completion() - }) + }, fromBackground: fromBackground) } func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) { @@ -707,6 +723,133 @@ public final class EmojiStatusSelectionController: ViewController { self.cloudShadowLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) } } + + if let previewItem = self.previewItem, let itemFile = previewItem.item.itemFile { + let previewScreenView: ComponentView + var previewScreenTransition = transition + if let current = self.previewScreenView { + previewScreenView = current + } else { + previewScreenTransition = Transition(animation: .none) + if let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem(groupId: previewItem.groupId, item: previewItem.item) { + previewScreenTransition = previewScreenTransition.withUserData(EmojiStatusPreviewScreenComponent.TransitionAnimation( + transitionType: .animateIn(sourceLayer: sourceLayer) + )) + } + previewScreenView = ComponentView() + self.previewScreenView = previewScreenView + } + let _ = previewScreenView.update( + transition: previewScreenTransition, + component: AnyComponent(EmojiStatusPreviewScreenComponent( + theme: self.presentationData.theme, + strings: self.presentationData.strings, + item: EmojiStatusComponent( + context: self.context, + animationCache: self.context.animationCache, + animationRenderer: self.context.animationRenderer, + content: .animation( + content: .file(file: itemFile), + size: CGSize(width: 128.0, height: 128.0), + placeholderColor: self.presentationData.theme.list.plainBackgroundColor.withMultipliedAlpha(0.1), + themeColor: self.presentationData.theme.list.itemAccentColor, + loopMode: .forever + ), + isVisibleForAnimations: true, + useSharedAnimation: false, + action: nil + ), + dismiss: { [weak self] result in + guard let strongSelf = self else { + return + } + + if let result = result, let previewItem = strongSelf.previewItem { + var emojiString: String? + if let itemFile = previewItem.item.itemFile { + attributeLoop: for attribute in itemFile.attributes { + switch attribute { + case let .CustomEmoji(_, alt, _): + emojiString = alt + break attributeLoop + default: + break + } + } + } + + let context = strongSelf.context + let _ = (context.engine.stickers.availableReactions() + |> take(1) + |> mapToSignal { availableReactions -> Signal in + guard let emojiString = emojiString, let availableReactions = availableReactions else { + return .single(nil) + } + for reaction in availableReactions.reactions { + if case let .builtin(value) = reaction.value, value == emojiString { + if let aroundAnimation = reaction.aroundAnimation { + return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + |> take(1) + |> map { data -> String? in + if data.complete { + return data.path + } else { + return nil + } + } + } else { + return .single(nil) + } + } + } + return .single(nil) + } + |> deliverOnMainQueue).start(next: { filePath in + guard let strongSelf = self, let previewItem = strongSelf.previewItem, let destinationView = strongSelf.controller?.destinationItemView() else { + return + } + + var expirationDate: Int32? + if result.timeout > 0 { + expirationDate = Int32(Date().timeIntervalSince1970) + Int32(result.timeout) + } + let _ = (strongSelf.context.engine.accountData.setEmojiStatus(file: previewItem.item.itemFile, expirationDate: expirationDate) + |> deliverOnMainQueue).start() + + strongSelf.animateOutToStatus(item: previewItem.item, sourceLayer: result.sourceView.layer, customEffectFile: filePath, destinationView: destinationView, fromBackground: true) + }) + } else { + strongSelf.dismissedPreviewItem = strongSelf.previewItem + strongSelf.previewItem = nil + strongSelf.refreshLayout(transition: .immediate) + } + } + )), + environment: {}, + containerSize: layout.size + ) + if let view = previewScreenView.view { + if view.superview == nil { + self.view.addSubview(view) + } + transition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: layout.size)) + } + } else if let previewScreenView = self.previewScreenView { + self.previewScreenView = nil + + if let previewScreenComponentView = previewScreenView.view as? EmojiStatusPreviewScreenComponent.View { + var targetLayer: CALayer? + if let previewItem = self.dismissedPreviewItem, let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem(groupId: previewItem.groupId, item: previewItem.item) { + targetLayer = sourceLayer + } + + previewScreenComponentView.animateOut(targetLayer: targetLayer, completion: { [weak previewScreenComponentView] in + previewScreenComponentView?.removeFromSuperview() + }) + } else { + previewScreenView.view?.removeFromSuperview() + } + } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -725,102 +868,114 @@ public final class EmojiStatusSelectionController: ViewController { return nil } - private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?) { + private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?, isPreview: Bool) { guard let controller = self.controller else { return } - self.freezeUpdates = true - - if let _ = item, let destinationView = controller.destinationItemView() { - if let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = destinationView.frame - destinationView.superview?.insertSubview(snapshotView, belowSubview: destinationView) - snapshotView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) + if isPreview { + guard let item = item else { + return } - destinationView.isHidden = true - } - - switch controller.mode { - case .statusSelection: - let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil) - |> deliverOnMainQueue).start() - case let .quickReactionSelection(completion): - if let item = item, let itemFile = item.itemFile { - var selectedReaction: MessageReaction.Reaction? + self.previewItem = (groupId, item) + self.refreshLayout(transition: .immediate) + } else { + self.freezeUpdates = true + + if let _ = item, let destinationView = controller.destinationItemView() { + if let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = destinationView.frame + destinationView.superview?.insertSubview(snapshotView, belowSubview: destinationView) + snapshotView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + destinationView.isHidden = true + } + + switch controller.mode { + case .statusSelection: + let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil) + |> deliverOnMainQueue).start() + case let .quickReactionSelection(completion): + if let item = item, let itemFile = item.itemFile { + var selectedReaction: MessageReaction.Reaction? + + if let availableReactions = self.availableReactions { + for reaction in availableReactions.reactions { + if reaction.selectAnimation.fileId == itemFile.fileId { + selectedReaction = reaction.value + break + } + } + } + + if selectedReaction == nil { + selectedReaction = .custom(itemFile.fileId.id) + } + + if let selectedReaction = selectedReaction { + let _ = context.engine.stickers.updateQuickReaction(reaction: selectedReaction).start() + } + } - if let availableReactions = self.availableReactions { - for reaction in availableReactions.reactions { - if reaction.selectAnimation.fileId == itemFile.fileId { - selectedReaction = reaction.value + completion() + } + + if let item = item, let destinationView = controller.destinationItemView() { + var emojiString: String? + if let itemFile = item.itemFile { + attributeLoop: for attribute in itemFile.attributes { + switch attribute { + case let .CustomEmoji(_, alt, _): + emojiString = alt + break attributeLoop + default: break } } } - if selectedReaction == nil { - selectedReaction = .custom(itemFile.fileId.id) - } - - if let selectedReaction = selectedReaction { - let _ = context.engine.stickers.updateQuickReaction(reaction: selectedReaction).start() - } - } - - completion() - } - - if let item = item, let destinationView = controller.destinationItemView() { - var emojiString: String? - if let itemFile = item.itemFile { - attributeLoop: for attribute in itemFile.attributes { - switch attribute { - case let .CustomEmoji(_, alt, _): - emojiString = alt - break attributeLoop - default: - break + let context = self.context + let _ = (context.engine.stickers.availableReactions() + |> take(1) + |> mapToSignal { availableReactions -> Signal in + guard let emojiString = emojiString, let availableReactions = availableReactions else { + return .single(nil) } - } - } - - let context = self.context - let _ = (context.engine.stickers.availableReactions() - |> take(1) - |> mapToSignal { availableReactions -> Signal in - guard let emojiString = emojiString, let availableReactions = availableReactions else { - return .single(nil) - } - for reaction in availableReactions.reactions { - if case let .builtin(value) = reaction.value, value == emojiString { - if let aroundAnimation = reaction.aroundAnimation { - return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) - |> take(1) - |> map { data -> String? in - if data.complete { - return data.path - } else { - return nil + for reaction in availableReactions.reactions { + if case let .builtin(value) = reaction.value, value == emojiString { + if let aroundAnimation = reaction.aroundAnimation { + return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + |> take(1) + |> map { data -> String? in + if data.complete { + return data.path + } else { + return nil + } } + } else { + return .single(nil) } - } else { - return .single(nil) } } + return .single(nil) } - return .single(nil) + |> deliverOnMainQueue).start(next: { [weak self] filePath in + guard let strongSelf = self else { + return + } + guard let emojiView = strongSelf.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem( groupId: groupId, item: item) else { + strongSelf.controller?.dismiss() + return + } + + strongSelf.animateOutToStatus(item: item, sourceLayer: sourceLayer, customEffectFile: filePath, destinationView: destinationView, fromBackground: false) + }) + } else { + controller.dismiss() } - |> deliverOnMainQueue).start(next: { [weak self] filePath in - guard let strongSelf = self else { - return - } - - strongSelf.animateOutToStatus(groupId: groupId, item: item, customEffectFile: filePath, destinationView: destinationView) - }) - } else { - controller.dismiss() } } } @@ -871,7 +1026,7 @@ public final class EmojiStatusSelectionController: ViewController { (self.displayNode as! Node).animateOut(completion: { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) completion?() - }) + }, fromBackground: false) } override public func loadDisplayNode() { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 96d5a5dadc..2e23e781b0 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -1451,11 +1451,15 @@ private final class GroupExpandActionButton: UIButton { super.touchesCancelled(touches, with: event) } - func update(theme: PresentationTheme, title: String) -> CGSize { + func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize { let textConstrainedWidth: CGFloat = 100.0 let color = theme.list.itemCheckColors.foregroundColor - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor + if useOpaqueTheme { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor + } else { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor + } self.tintContainerLayer.backgroundColor = UIColor.white.cgColor let textSize: CGSize @@ -1576,6 +1580,7 @@ public final class EmojiPagerContentComponent: Component { public let peekBehavior: EmojiContentPeekBehavior? public let customLayout: CustomLayout? public let externalBackground: ExternalBackground? + public let useOpaqueTheme: Bool public init( performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void, @@ -1592,7 +1597,8 @@ public final class EmojiPagerContentComponent: Component { chatPeerId: PeerId?, peekBehavior: EmojiContentPeekBehavior?, customLayout: CustomLayout?, - externalBackground: ExternalBackground? + externalBackground: ExternalBackground?, + useOpaqueTheme: Bool ) { self.performItemAction = performItemAction self.deleteBackwards = deleteBackwards @@ -1609,6 +1615,7 @@ public final class EmojiPagerContentComponent: Component { self.peekBehavior = peekBehavior self.customLayout = customLayout self.externalBackground = externalBackground + self.useOpaqueTheme = useOpaqueTheme } } @@ -3385,8 +3392,9 @@ public final class EmojiPagerContentComponent: Component { self.hapticFeedback = HapticFeedback() } - let transition = Transition(animation: .curve(duration: longPressDuration, curve: .easeInOut)) - transition.setScale(layer: itemLayer, scale: 1.3) + let _ = itemLayer + //let transition = Transition(animation: .curve(duration: longPressDuration, curve: .easeInOut)) + //transition.setScale(layer: itemLayer, scale: 1.3) self.longPressTimer?.invalidate() self.longPressTimer = SwiftSignalKit.Timer(timeout: longPressDuration, repeat: false, completion: { [weak self] in @@ -3576,6 +3584,8 @@ public final class EmojiPagerContentComponent: Component { return } + let useOpaqueTheme = component.inputInteractionHolder.inputInteraction?.useOpaqueTheme ?? false + var topVisibleGroupId: AnyHashable? var topVisibleSubgroupId: AnyHashable? @@ -3869,7 +3879,7 @@ public final class EmojiPagerContentComponent: Component { } let baseItemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: collapsedItemIndex) - let buttonSize = groupExpandActionButton.update(theme: keyboardChildEnvironment.theme, title: collapsedItemText) + let buttonSize = groupExpandActionButton.update(theme: keyboardChildEnvironment.theme, title: collapsedItemText, useOpaqueTheme: useOpaqueTheme) let buttonFrame = CGRect(origin: CGPoint(x: baseItemFrame.minX + floor((baseItemFrame.width - buttonSize.width) / 2.0), y: baseItemFrame.minY + floor((baseItemFrame.height - buttonSize.height) / 2.0)), size: buttonSize) groupExpandActionButtonTransition.setFrame(view: groupExpandActionButton, frame: buttonFrame) } @@ -4050,8 +4060,19 @@ public final class EmojiPagerContentComponent: Component { self.visibleItemSelectionLayers[itemId] = itemSelectionLayer } - itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor - itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + if item.accentTint { + itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor + itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor + } else { + if useOpaqueTheme { + itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor + itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.clear.cgColor + } else { + itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor + itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + } + } + itemSelectionLayer.frame = baseItemFrame itemLayer.transform = CATransform3DMakeScale(0.8, 0.8, 1.0) @@ -4771,9 +4792,22 @@ public final class EmojiPagerContentComponent: Component { orderedItemListCollectionIds.append(Namespaces.OrderedItemList.LocalRecentEmoji) + var iconStatusEmoji: Signal<[TelegramMediaFile], NoError> = .single([]) + if isStatusSelection { orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji) + + iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false) + |> map { result -> [TelegramMediaFile] in + switch result { + case let .result(_, items, _): + return items.map(\.file) + default: + return [] + } + } + |> take(1) } else if isReactionSelection { orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions) @@ -4790,9 +4824,10 @@ public final class EmojiPagerContentComponent: Component { context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true), context.account.viewTracker.featuredEmojiPacks(), - availableReactions + availableReactions, + iconStatusEmoji ) - |> map { view, hasPremium, featuredEmojiPacks, availableReactions -> EmojiPagerContentComponent in + |> map { view, hasPremium, featuredEmojiPacks, availableReactions, iconStatusEmoji -> EmojiPagerContentComponent in struct ItemGroup { var supergroupId: AnyHashable var id: AnyHashable @@ -4842,10 +4877,49 @@ public final class EmojiPagerContentComponent: Component { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) + //TODO:localize + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Long tap to set a timer".uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } var existingIds = Set() + + for file in iconStatusEmoji { + if existingIds.contains(file.fileId) { + continue + } + existingIds.insert(file.fileId) + + var accentTint = false + for attribute in file.attributes { + if case let .CustomEmoji(_, _, packReference) = attribute { + switch packReference { + case let .id(id, _): + if id == 773947703670341676 || id == 2964141614563343 { + accentTint = true + } + default: + break + } + } + } + + let resultItem: EmojiPagerContentComponent.Item + + let animationData = EntityKeyboardAnimationData(file: file) + resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: file, + subgroupId: nil, + icon: .none, + accentTint: accentTint + ) + + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } + } + if let recentStatusEmoji = recentStatusEmoji { for item in recentStatusEmoji.items { guard let item = item.contents.get(RecentMediaItem.self) else { @@ -4863,7 +4937,7 @@ public final class EmojiPagerContentComponent: Component { if case let .CustomEmoji(_, _, packReference) = attribute { switch packReference { case let .id(id, _): - if id == 773947703670341676 { + if id == 773947703670341676 || id == 2964141614563343 { accentTint = true } default: @@ -4908,7 +4982,7 @@ public final class EmojiPagerContentComponent: Component { if case let .CustomEmoji(_, _, packReference) = attribute { switch packReference { case let .id(id, _): - if id == 773947703670341676 { + if id == 773947703670341676 || id == 2964141614563343 { accentTint = true } default: @@ -5377,7 +5451,7 @@ public final class EmojiPagerContentComponent: Component { }, itemLayoutType: .compact, warpContentsOnEdges: isReactionSelection || isStatusSelection, - enableLongPress: isReactionSelection && !isQuickReactionSelection, + enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection, selectedItems: selectedItems ) } diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 501229552c..bd2bb99839 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -1104,7 +1104,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId, peekBehavior: nil, customLayout: nil, - externalBackground: nil + externalBackground: nil, + useOpaqueTheme: false ) var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? @@ -1308,7 +1309,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId, peekBehavior: stickerPeekBehavior, customLayout: nil, - externalBackground: nil + externalBackground: nil, + useOpaqueTheme: false ) self.inputDataDisposable = (combineLatest(queue: .mainQueue(), @@ -2017,7 +2019,8 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV chatPeerId: nil, peekBehavior: nil, customLayout: nil, - externalBackground: nil + externalBackground: nil, + useOpaqueTheme: false ) let semaphore = DispatchSemaphore(value: 0) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 4fd01a8600..36ac1507d8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3105,6 +3105,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let animationRenderer = context.animationRenderer strongSelf.emojiStatusSelectionController?.dismiss() + var selectedItems = Set() + if let peer = strongSelf.data?.peer { + if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus { + selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)) + } + } let emojiStatusSelectionController = EmojiStatusSelectionController( context: strongSelf.context, mode: .statusSelection, @@ -3119,7 +3125,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate topReactionItems: [], areUnicodeEmojiEnabled: false, areCustomEmojiEnabled: true, - chatPeerId: strongSelf.context.account.peerId + chatPeerId: strongSelf.context.account.peerId, + selectedItems: selectedItems ), destinationItemView: { [weak sourceView] in return sourceView