import Foundation import TelegramPresentationData import AccountContext import Postbox import TelegramCore import SwiftSignalKit import ContextUI import ChatControllerInteraction import Display import UIKit import UndoUI import ShareController import ChatQrCodeScreen import ChatShareMessageTagView import ReactionSelectionNode func chatShareToSavedMessagesAdditionalView(_ chatController: ChatControllerImpl, reactionItems: [ReactionItem], correlationId: Int64?) -> (() -> UndoOverlayControllerAdditionalView?)? { guard let correlationId else { return nil } return { [weak chatController] () -> UndoOverlayControllerAdditionalView? in guard let chatController else { return nil } return ChatShareMessageTagView(context: chatController.context, presentationData: chatController.presentationData, reactionItems: reactionItems, completion: { [weak chatController] file, updateReaction in guard let chatController else { return } let _ = (chatController.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: chatController.context.account.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 45, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: []) |> map { view, _, _ -> [EngineMessage.Id] in guard let messageId = chatController.context.engine.messages.synchronouslyLookupCorrelationId(correlationId: correlationId) else { return [] } let exactResult = view.entries.compactMap { entry -> EngineMessage.Id? in if entry.message.id == messageId { return entry.message.id } else { return nil } } if !exactResult.isEmpty { return exactResult } return [] } |> filter { !$0.isEmpty } |> take(1) |> timeout(5.0, queue: .mainQueue(), alternate: .single([])) |> deliverOnMainQueue).start(next: { [weak chatController] messageIds in guard let chatController else { return } if let messageId = messageIds.first { let _ = chatController.context.engine.messages.setMessageReactions(id: messageId, reactions: [updateReaction]) var isBuiltinReaction = false if case .builtin = updateReaction { isBuiltinReaction = true } let presentationData = chatController.context.sharedContext.currentPresentationData.with { $0 } chatController.present(UndoOverlayController(presentationData: presentationData, content: .messageTagged(context: chatController.context, customEmoji: file, isBuiltinReaction: isBuiltinReaction), elevatedLayout: false, position: .top, animateInAsReplacement: true, action: { _ in return false }), in: .current) } }) }) } } extension ChatControllerImpl { func openMessageShareMenu(id: EngineMessage.Id) { guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let message = messages.first else { return } let chatPresentationInterfaceState = self.presentationInterfaceState var warnAboutPrivate = false var canShareToStory = false if case .peer = chatPresentationInterfaceState.chatLocation, let channel = message.peers[message.id.peerId] as? TelegramChannel { if case .broadcast = channel.info { canShareToStory = true } if channel.addressName == nil { warnAboutPrivate = true } } let shareController = ShareController(context: self.context, subject: .messages(messages), updatedPresentationData: self.updatedPresentationData, shareAsLink: true) shareController.parentNavigationController = self.navigationController as? NavigationController if let message = messages.first, message.media.contains(where: { media in if media is TelegramMediaContact || media is TelegramMediaPoll { return true } else if let file = media as? TelegramMediaFile, file.isSticker || file.isAnimatedSticker || file.isVideoSticker { return true } else { return false } }) { canShareToStory = false } if message.text.containsOnlyEmoji { canShareToStory = false } if canShareToStory { shareController.shareStory = { [weak self] in guard let self else { return } Queue.mainQueue().after(0.15) { self.openStorySharing(messages: messages) } } } shareController.openShareAsImage = { [weak self] messages in guard let self else { return } self.present(ChatQrCodeScreen(context: self.context, subject: .messages(messages)), in: .window(.root)) } shareController.dismissed = { [weak self] shared in if shared { self?.commitPurposefulAction() } } shareController.actionCompleted = { [weak self] in guard let self else { return } let content: UndoOverlayContent if warnAboutPrivate { content = .linkCopied(text: self.presentationData.strings.Conversation_PrivateMessageLinkCopiedLong) } else { content = .linkCopied(text: self.presentationData.strings.Conversation_LinkCopied) } self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) } shareController.enqueued = { [weak self] peerIds, correlationIds in guard let self else { return } let _ = (self.context.engine.data.get( EngineDataList( peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) ) ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in guard let self else { return } let peers = peerList.compactMap { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let text: String var savedMessages = false if peerIds.count == 1, let peerId = peerIds.first, peerId == self.context.account.peerId { text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many savedMessages = true } else { if peers.count == 1, let peer = peers.first { var peerName = peer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) peerName = peerName.replacingOccurrences(of: "**", with: "") text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { var firstPeerName = firstPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") var secondPeerName = secondPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string } else if let peer = peers.first { var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) peerName = peerName.replacingOccurrences(of: "**", with: "") text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string } else { text = "" } } let reactionItems: Signal<[ReactionItem], NoError> if savedMessages { reactionItems = tagMessageReactions(context: self.context) } else { reactionItems = .single([]) } let _ = (reactionItems |> deliverOnMainQueue).startStandalone(next: { [weak self] reactionItems in guard let self else { return } self.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages ? .top : .bottom, animateInAsReplacement: !savedMessages, action: { [weak self] action in if savedMessages, let self, action == .info { let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let peer else { return } guard let navigationController = self.navigationController as? NavigationController else { return } self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) }) } return false }, additionalView: savedMessages ? chatShareToSavedMessagesAdditionalView(self, reactionItems: reactionItems, correlationId: correlationIds.first) : nil), in: .current) }) }) } self.chatDisplayNode.dismissInput() self.present(shareController, in: .window(.root), blockInteraction: true) } }