From df5fba7d51b6b754f796b18b531fb9f8e7dfeb3c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 27 Jan 2023 15:38:20 +0100 Subject: [PATCH] Emoji preview --- .../Sources/StickerPackEmojisItem.swift | 17 +- .../Sources/StickerPackScreen.swift | 193 ++++++++++++++++++ 2 files changed, 208 insertions(+), 2 deletions(-) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 60c0471896..0d5113526a 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -192,7 +192,7 @@ final class StickerPackEmojisItemNode: GridItemNode { } self.boundsChangeTrackerLayer = boundsChangeTrackerLayer - let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + /*let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) gestureRecognizer.longTap = { [weak self] point, _ in guard let strongSelf = self else { return @@ -217,7 +217,20 @@ final class StickerPackEmojisItemNode: GridItemNode { } } } - self.containerNode.view.addGestureRecognizer(gestureRecognizer) + self.containerNode.view.addGestureRecognizer(gestureRecognizer)*/ + } + + func targetItem(at point: CGPoint) -> (TelegramMediaFile, CALayer)? { + if let (item, _) = self.item(atPoint: point), let file = item.itemFile { + let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( + groupId: 0, + itemId: .animation(.file(file.fileId)) + ) + if let itemLayer = self.visibleItemLayers[itemId] { + return (file, itemLayer) + } + } + return nil } @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 3ca11e0a57..705acf40d3 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -97,6 +97,7 @@ private final class StickerPackContainer: ASDisplayNode { private let requestDismiss: () -> Void private let presentInGlobalOverlay: (ViewController, Any?) -> Void private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? + private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? private let backgroundNode: ASImageNode private let gridNode: GridNode private let actionAreaBackgroundNode: NavigationBackgroundNode @@ -172,6 +173,7 @@ private final class StickerPackContainer: ASDisplayNode { self.presentInGlobalOverlay = presentInGlobalOverlay self.expandProgressUpdated = expandProgressUpdated self.sendSticker = sendSticker + self.sendEmoji = sendEmoji self.backgroundNode = ASImageNode() self.backgroundNode.displaysAsynchronously = true @@ -494,6 +496,8 @@ private final class StickerPackContainer: ASDisplayNode { return nil } } + } else if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackEmojisItemNode, let targetItem = itemNode.targetItem(at: strongSelf.gridNode.view.convert(point, to: itemNode.view)) { + return strongSelf.emojiSuggestionPeekContent(itemLayer: targetItem.1, file: targetItem.0) } } return nil @@ -523,6 +527,195 @@ private final class StickerPackContainer: ASDisplayNode { }, activateBySingleTap: true)) } + private func emojiSuggestionPeekContent(itemLayer: CALayer, file: TelegramMediaFile) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError> { + let context = self.context + + var collectionId: ItemCollectionId? + for attribute in file.attributes { + if case let .CustomEmoji(_, _, _, packReference) = attribute { + switch packReference { + case let .id(id, _): + collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + default: + break + } + } + } + + var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + if let collectionId { + bubbleUpEmojiOrStickersets.append(collectionId) + } + + let accountPeerId = context.account.peerId + + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) + |> map { peer -> Bool in + var hasPremium = false + if case let .user(user) = peer, user.isPremium { + hasPremium = true + } + return hasPremium + } + |> deliverOnMainQueue + |> map { [weak self, weak itemLayer] hasPremium -> (UIView, CGRect, PeekControllerContent)? in + guard let strongSelf = self, let itemLayer = itemLayer else { + return nil + } + + var menuItems: [ContextMenuItem] = [] + menuItems.removeAll() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var isLocked = false + if !hasPremium { + isLocked = file.isPremiumEmoji + /*if isLocked && chatPeerId == context.account.peerId { + isLocked = false + }*/ + } + + let sendEmoji: (TelegramMediaFile) -> Void = { file in + guard let self else { + return + } + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, _, displayText, stickerPackReference): + text = displayText + + var packId: ItemCollectionId? + if case let .id(id, _) = stickerPackReference { + packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + } + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let emojiAttribute { + self.sendEmoji?(text, emojiAttribute) + } + } + let setStatus: (TelegramMediaFile) -> Void = { file in + guard let self else { + return + } + guard let controller = self.controller else { + return + } + + let context = self.context + + let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start() + + var animateInAsReplacement = false + animateInAsReplacement = false + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + //TODO:localize + let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Your emoji status has been updated.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false }) + controller.present(undoController, in: .window(.root)) + } + let copyEmoji: (TelegramMediaFile) -> Void = { file in + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, _, displayText, _): + text = displayText + + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let _ = emojiAttribute { + storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))]) + } + } + + //TODO:localize + if strongSelf.sendEmoji != nil { + menuItems.append(.action(ContextMenuActionItem(text: "Send Emoji", icon: { theme in + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) { + return generateImage(image.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) + } + }) + } else { + return nil + } + }, action: { _, f in + sendEmoji(file) + f(.default) + }))) + } + + //TODO:localize + menuItems.append(.action(ContextMenuActionItem(text: "Set as Status", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.default) + + guard let strongSelf = self else { + return + } + + if hasPremium { + setStatus(file) + } else { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: { + let controller = PremiumIntroScreen(context: context, source: .animatedEmoji) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.controller?.push(controller) + } + }))) + + //TODO:localize + menuItems.append(.action(ContextMenuActionItem(text: "Copy Emoji", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + copyEmoji(file) + f(.default) + }))) + + if menuItems.isEmpty { + return nil + } + + let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in + guard let self else { + return + } + guard let controller = self.controller else { + return + } + + let premiumController = PremiumIntroScreen(context: context, source: .stickers) + controller.push(premiumController) + }) + + return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content) + } + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData