import Foundation import UIKit import SwiftSignalKit import Postbox import TelegramCore import AsyncDisplayKit import Display import TelegramNotices import ContextUI import AccountContext import ChatMessageItemView import ChatMessageItemCommon import AvatarNode import UndoUI import MessageUI import PeerInfoUI extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { func openPhoneContextMenu(number: String, peer: EnginePeer?, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { if self.presentationInterfaceState.interfaceState.selectionState != nil { return } self.dismissAllTooltips() let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer let gesture: ContextGesture? = anyRecognizer as? ContextGesture if let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) { (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() self.chatDisplayNode.cancelInteractiveKeyboardGestures() var updatedMessages = messages for i in 0 ..< updatedMessages.count { if updatedMessages[i].id == message.id { let message = updatedMessages.remove(at: i) updatedMessages.insert(message, at: 0) break } } self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() let source: ContextContentSource if let location = location { source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) } else { source = .extracted(ChatMessagePhoneContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) // source = .extracted(ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false)) } var items: [ContextMenuItem] = [] items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_AddToContacts, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self, let c else { return } let basicData = DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [ DeviceContactPhoneNumberData(label: "", value: number) ]) let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: nil, contactData: contactData, parentController: self, push: { [weak self] c in self?.push(c) }) })) ) items.append(.separator) if let peer { if peer.id == self.context.account.peerId { } else { items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) })) ) items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVoiceCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } self.controllerInteraction?.callPeer(peer.id, false) })) ) items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVideoCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } self.controllerInteraction?.callPeer(peer.id, true) })) ) } } else { items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_InviteToTelegram, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Telegram"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } self.inviteToTelegram(numbers: [number]) })) ) } if number.hasPrefix("+888") { } else { items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CallViaCarrier, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { return } self.openUrl("tel:\(number)", concealed: false) })) ) } items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CopyNumber, 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 } UIPasteboard.general.string = number self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) })) ) items.append(.separator) if let peer { let avatarSize = CGSize(width: 28.0, height: 28.0) let avatarSignal = peerAvatarCompleteImage(account: self.context.account, peer: peer, size: avatarSize) let subtitle = NSMutableAttributedString(string: self.presentationData.strings.Chat_Context_Phone_ViewProfile + " >") if let range = subtitle.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { subtitle.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitle.string)) subtitle.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitle.string)) } items.append( .action(ContextMenuActionItem(text: peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), textLayout: .secondLineWithAttributedValue(subtitle), icon: { theme in return nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), iconPosition: .left, action: { [weak self] _, f in f(.default) guard let self else { return } self.openPeer(peer: peer, navigation: .info(nil), fromMessage: nil) })) ) } else { let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil items.append( .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_NotOnTelegram, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)) ) } self.canReadHistory.set(false) let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false) controller.dismissed = { [weak self] in self?.canReadHistory.set(true) } self.window?.presentInGlobalOverlay(controller) } } private func inviteToTelegram(numbers: [String]) { if MFMessageComposeViewController.canSendText() { let composer = MFMessageComposeViewController() composer.messageComposeDelegate = self composer.recipients = Array(Set(numbers)) let url = self.presentationData.strings.InviteText_URL let body = self.presentationData.strings.InviteText_SingleContact(url).string composer.body = body self.messageComposeController = composer if let window = self.view.window { window.rootViewController?.present(composer, animated: true) } } } @objc public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { self.messageComposeController = nil controller.dismiss(animated: true, completion: nil) } } private final class ChatMessagePhoneContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = false let ignoreContentTouches: Bool = true let blurBackground: Bool = true let adjustContentHorizontally = true private weak var chatNode: ChatControllerNode? private let contentNode: ContextExtractedContentContainingNode var shouldBeDismissed: Signal { return .single(false) } init(chatNode: ChatControllerNode, contentNode: ContextExtractedContentContainingNode) { self.chatNode = chatNode self.contentNode = contentNode } func takeView() -> ContextControllerTakeViewInfo? { guard let chatNode = self.chatNode else { return nil } let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) transition.updateAlpha(node: self.contentNode.contentNode, alpha: 1.0) return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } func putBack() -> ContextControllerPutBackViewInfo? { guard let chatNode = self.chatNode else { return nil } let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) transition.updateAlpha(node: self.contentNode.contentNode, alpha: 0.0, completion: { _ in self.contentNode.removeFromSupernode() }) return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } }