diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift index a93adb17bc..4d00e0b9d7 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift @@ -233,6 +233,31 @@ public final class EmojiSuggestionsComponent: Component { fatalError("init(coder:) has not been implemented") } + public func item(at point: CGPoint) -> (CALayer, TelegramMediaFile)? { + let location = self.convert(point, to: self.scrollView) + if self.scrollView.bounds.contains(location) { + var closestFile: (file: TelegramMediaFile, layer: CALayer, distance: CGFloat)? + for (_, itemLayer) in self.visibleLayers { + guard let file = itemLayer.file else { + continue + } + let distance = abs(location.x - itemLayer.position.x) + if let (_, _, currentDistance) = closestFile { + if distance < currentDistance { + closestFile = (file, itemLayer, distance) + } + } else { + closestFile = (file, itemLayer, distance) + } + } + if let (file, itemLayer, _) = closestFile { + return (itemLayer, file) + } + } + + return nil + } + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { let location = recognizer.location(in: self.scrollView) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 3902434dfb..5c3ace2355 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -196,7 +196,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } - if isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) { + if case .group = channel.info, isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) { if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) { return (currentPanel, nil) } else { diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index 2b88e19118..25cad550c1 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -30,17 +30,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.presentationInterfaceState = interfaceState } - let bannedPermission: (Int32, Bool)? + var bannedPermission: (Int32, Bool)? if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel { - bannedPermission = channel.hasBannedPermission(.banSendText) - } else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup { - if group.hasBannedPermission(.banSendText) { + if let value = channel.hasBannedPermission(.banSendText) { + bannedPermission = value + } else if !channel.hasPermission(.sendSomething) { + bannedPermission = (Int32.max, false) + } + } else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup { + if !group.hasPermission(.sendSomething) { bannedPermission = (Int32.max, false) - } else { - bannedPermission = nil } - } else { - bannedPermission = nil } var iconImage: UIImage? diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 6aa729b850..ec011420a2 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -30,6 +30,9 @@ import ComponentFlow import EmojiSuggestionsComponent import AudioToolbox import ChatControllerInteraction +import UndoUI +import PremiumUI +import StickerPeekUI private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -2571,6 +2574,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { viewForOverlayContent.addSubview(currentEmojiSuggestionView) currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + + self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView) } let globalPosition = textInputNode.textView.convert(currentEmojiSuggestion.localPosition, to: self.view) @@ -2693,6 +2698,277 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } + private func installEmojiSuggestionPreviewGesture(hostView: UIView) { + let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in + guard let self else { + return nil + } + return self.emojiSuggestionPeekContentAtPoint(point: point) + }, present: { [weak self] content, sourceView, sourceRect in + guard let strongSelf = self, let context = strongSelf.context else { + return nil + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) + }) + //strongSelf.peekController = controller + strongSelf.interfaceInteraction?.presentController(controller, nil) + return controller + }, updateContent: { [weak self] content in + guard let strongSelf = self else { + return + } + + let _ = strongSelf + }) + hostView.addGestureRecognizer(peekRecognizer) + } + + private func emojiSuggestionPeekContentAtPoint(point: CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? { + guard let presentationInterfaceState = self.presentationInterfaceState else { + return nil + } + guard let chatPeerId = presentationInterfaceState.renderedPeer?.peer?.id else { + return nil + } + guard let context = self.context else { + return nil + } + + var maybeFile: TelegramMediaFile? + var maybeItemLayer: CALayer? + + if let currentEmojiSuggestionView = self.currentEmojiSuggestionView?.componentView as? EmojiSuggestionsComponent.View { + if let (itemLayer, file) = currentEmojiSuggestionView.item(at: point) { + maybeFile = file + maybeItemLayer = itemLayer + } + } + + guard let file = maybeFile else { + return nil + } + guard let itemLayer = maybeItemLayer else { + return nil + } + + let _ = chatPeerId + let _ = file + let _ = itemLayer + + 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 + + let _ = bubbleUpEmojiOrStickersets + let _ = context + let _ = accountPeerId + + 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 + } + + let _ = strongSelf + let _ = itemLayer + + var menuItems: [ContextMenuItem] = [] + menuItems.removeAll() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = presentationData + + var isLocked = false + if !hasPremium { + isLocked = file.isPremiumEmoji + if isLocked && chatPeerId == context.account.peerId { + isLocked = false + } + } + + if let interaction = strongSelf.interfaceInteraction { + let _ = interaction + + let sendEmoji: (TelegramMediaFile) -> Void = { file in + guard let self else { + return + } + guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) 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 { + controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true) + } + } + let setStatus: (TelegramMediaFile) -> Void = { file in + guard let self, let context = self.context else { + return + } + guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else { + return + } + + let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start() + + var animateInAsReplacement = false + animateInAsReplacement = false + /*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { + currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() + strongSelf.currentUndoOverlayController = nil + animateInAsReplacement = true + }*/ + + 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 }) + //strongSelf.currentUndoOverlayController = controller + controller.controllerInteraction?.presentController(undoController, nil) + } + 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 + 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.interfaceInteraction?.getNavigationController()?.pushViewController(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 interfaceInteraction = self.interfaceInteraction else { + return + } + + let _ = self + let _ = interfaceInteraction + + let controller = PremiumIntroScreen(context: context, source: .stickers) + //let _ = controller + + interfaceInteraction.getNavigationController()?.pushViewController(controller) + }) + let _ = content + //return nil + + return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content) + } + } + private func updateTextNodeText(animated: Bool) { var inputHasText = false var hideMicButton = false