import Foundation import UIKit import SwiftSignalKit import Postbox import TelegramCore import AsyncDisplayKit import Display import ContextUI import UndoUI import AccountContext import ChatMessageItemView import ChatMessageItemCommon import ChatControllerInteraction import Pasteboard import TelegramStringFormatting import TelegramPresentationData private enum OptionsId: Hashable { case item case message } extension ChatControllerImpl { func openPollOptionContextMenu(optionId: Data, params: ChatControllerInteraction.LongTapParams) -> Void { guard let message = params.message, let poll = message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll, let pollOptionIndex = poll.options.firstIndex(where: { $0.opaqueIdentifier == optionId }), let contentNode = params.contentNode else { return } let pollOption = poll.options[pollOptionIndex] var selectedOptions: [Data] = [] if let voters = poll.results.voters { for voter in voters { if voter.selected { selectedOptions.append(voter.opaqueIdentifier) } } } let _ = (contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView) |> deliverOnMainQueue).start(next: { [weak self] actions in guard let self else { return } var items: [ContextMenuItem] = [] if !poll.isClosed && (selectedOptions.isEmpty || !poll.revotingDisabled) { if selectedOptions.contains(pollOption.opaqueIdentifier) { items.append(.action(ContextMenuActionItem(text: "Retract Vote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unvote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { return } c?.dismiss(result: .default, completion: { var updatedOptions = selectedOptions updatedOptions.removeAll(where: { $0 == pollOption.opaqueIdentifier } ) self.controllerInteraction?.requestSelectMessagePollOptions(message.id, updatedOptions) }) }))) } else { items.append(.action(ContextMenuActionItem(text: "Vote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StopPoll"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { return } c?.dismiss(result: .default, completion: { var updatedOptions = selectedOptions if !poll.kind.multipleAnswers { updatedOptions = [] } updatedOptions.append(pollOption.opaqueIdentifier) self.controllerInteraction?.requestSelectMessagePollOptions(message.id, updatedOptions) }) }))) } } //TODO:localize if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) { items.append(.action(ContextMenuActionItem(text: "Reply to Option", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] c, _ in guard let self else { return } self.interfaceInteraction?.setupReplyMessage(message.id, .pollOption(pollOption.opaqueIdentifier), { transition, completed in c?.dismiss(result: .custom(transition), completion: { completed() }) }) }))) } items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } storeMessageTextInPasteboard(pollOption.text, entities: pollOption.entities) self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) }))) var isReplyThreadHead = false if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation { isReplyThreadHead = message.id == replyThreadMessage.effectiveTopId } if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !isReplyThreadHead { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in guard let self else { return } var threadMessageId: MessageId? if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation { threadMessageId = replyThreadMessage.effectiveMessageId } let _ = (self.context.engine.messages.exportMessageLink(peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil) |> map { result -> String? in return result } |> deliverOnMainQueue).startStandalone(next: { [weak self] link in guard let self, let link else { return } UIPasteboard.general.string = link + "?option=\(pollOptionIndex)" let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var warnAboutPrivate = false if case .peer = self.presentationInterfaceState.chatLocation { if channel.addressName == nil { warnAboutPrivate = true } } Queue.mainQueue().after(0.2, { if warnAboutPrivate { self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_PrivateMessageLinkCopiedLong)) } else { self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied)) } }) }) f(.default) }))) } if pollOption.date != nil { //TODO:localize items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } let _ = self.context.engine.messages.deletePollOption(messageId: message.id, opaqueIdentifier: pollOption.opaqueIdentifier).start() }))) } self.canReadHistory.set(false) //TODO:localize var sources: [ContextController.Source] = [] sources.append( ContextController.Source( id: AnyHashable(OptionsId.item), title: "Option", footer: "You're viewing actions for one option.\nYou can switch to actions for the list.", source: .extracted(ChatTodoItemContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)), items: .single(ContextController.Items(content: .list(items))) ) ) let messageContentSource = ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, snapshot: true) sources.append( ContextController.Source( id: AnyHashable(OptionsId.message), title: "Poll", source: .extracted(messageContentSource), items: .single(actions) ) ) contentNode.onDismiss = { [weak messageContentSource] in messageContentSource?.snapshotView?.removeFromSuperview() } let contextController = makeContextController( presentationData: self.presentationData, configuration: ContextController.Configuration( sources: sources, initialId: AnyHashable(OptionsId.item) ) ) contextController.dismissed = { [weak self] in self?.canReadHistory.set(true) } self.window?.presentInGlobalOverlay(contextController) }) } }