From 7015c76e74c0fdac2597079b860f66fd1c6b14ab Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Sep 2022 04:11:06 +0300 Subject: [PATCH] Add new phone context menu in user profile --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Sources/CallListController.swift | 2 +- .../Sources/ChatListController.swift | 2 +- .../Sources/ContactsController.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 14 +- ...tControllerExtractedPresentationNode.swift | 40 +++-- ...essageContextControllerContentSource.swift | 4 +- .../PeerInfoScreenLabeledValueItem.swift | 110 ++++++++++-- .../Sources/PeerInfo/PeerInfoScreen.swift | 165 ++++++++++++++---- 9 files changed, 265 insertions(+), 75 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 833f8d1122..b39f1ace52 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2590,6 +2590,7 @@ Unused sets are archived when you add more."; "Conversation.HoldForVideo" = "Hold to record video. Tap to switch to audio."; "UserInfo.TelegramCall" = "Telegram Call"; +"UserInfo.TelegramVideoCall" = "Telegram Video Call"; "UserInfo.PhoneCall" = "Phone Call"; "SharedMedia.CategoryMedia" = "Media"; diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 501e8e1cc6..bcd23d8caa 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -499,7 +499,7 @@ private final class CallListTabBarContextExtractedContentSource: ContextExtracte let keepInPlace: Bool = true let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private let controller: ViewController private let sourceNode: ContextExtractedContentContainingNode diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0e6f2056aa..0944a9d917 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -3551,7 +3551,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte let keepInPlace: Bool = true let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private let controller: ChatListController private let sourceNode: ContextExtractedContentContainingNode diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index bbee69c4ba..2e1f0c103a 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -686,7 +686,7 @@ private final class ContactsTabBarContextExtractedContentSource: ContextExtracte let keepInPlace: Bool = true let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private let controller: ViewController private let sourceNode: ContextExtractedContentContainingNode diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index b5a6954f42..dd591819b7 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -2225,14 +2225,22 @@ public final class ContextControllerPutBackViewInfo { } } +public enum ContextActionsHorizontalAlignment { + case `default` + case left + case center + case right +} + public protocol ContextExtractedContentSource: AnyObject { var centerVertically: Bool { get } var keepInPlace: Bool { get } var ignoreContentTouches: Bool { get } var blurBackground: Bool { get } - var centerActionsHorizontally: Bool { get } var shouldBeDismissed: Signal { get } + var actionsHorizontalAlignment: ContextActionsHorizontalAlignment { get } + func takeView() -> ContextControllerTakeViewInfo? func putBack() -> ContextControllerPutBackViewInfo? } @@ -2242,8 +2250,8 @@ public extension ContextExtractedContentSource { return false } - var centerActionsHorizontally: Bool { - return false + var actionsHorizontalAlignment: ContextActionsHorizontalAlignment { + return .default } var shouldBeDismissed: Signal { diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index b81ca9f64d..0f983e673e 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -647,14 +647,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } let keepInPlace: Bool - let centerActionsHorizontally: Bool + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment switch self.source { case .location, .reference: keepInPlace = true - centerActionsHorizontally = false + actionsHorizontalAlignment = .default case let .extracted(source): keepInPlace = source.keepInPlace - centerActionsHorizontally = source.centerActionsHorizontally + actionsHorizontalAlignment = source.actionsHorizontalAlignment } var defaultScrollY: CGFloat = 0.0 @@ -769,7 +769,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let reactionContextNode = self.reactionContextNode { additionalVisibleOffsetY += reactionContextNode.visibleExtensionDistance } - if centerActionsHorizontally { + if case .center = actionsHorizontalAlignment { actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0) if actionsFrame.maxX > layout.size.width - actionsEdgeInset { actionsFrame.origin.x = layout.size.width - actionsEdgeInset - actionsFrame.width @@ -780,20 +780,24 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } else { if case .location = self.source { actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.minX + actionsSideInset - 4.0 - } else if contentRect.midX < layout.size.width / 2.0 { - actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.minX + actionsSideInset - 4.0 + } else if case .right = actionsHorizontalAlignment { + actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.maxX - actionsSideInset - actionsSize.width - 1.0 } else { - switch self.source { - case .location, .reference: - actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0) - if actionsFrame.maxX > layout.size.width - actionsEdgeInset { - actionsFrame.origin.x = layout.size.width - actionsEdgeInset - actionsFrame.width + if contentRect.midX < layout.size.width / 2.0 { + actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.minX + actionsSideInset - 4.0 + } else { + switch self.source { + case .location, .reference: + actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0) + if actionsFrame.maxX > layout.size.width - actionsEdgeInset { + actionsFrame.origin.x = layout.size.width - actionsEdgeInset - actionsFrame.width + } + if actionsFrame.minX < actionsEdgeInset { + actionsFrame.origin.x = actionsEdgeInset + } + case .extracted: + actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.maxX - actionsSideInset - actionsSize.width - 1.0 } - if actionsFrame.minX < actionsEdgeInset { - actionsFrame.origin.x = actionsEdgeInset - } - case .extracted: - actionsFrame.origin.x = contentParentGlobalFrame.minX + contentRect.maxX - actionsSideInset - actionsSize.width - 1.0 } } if actionsFrame.maxX > layout.size.width - actionsEdgeInset { @@ -900,7 +904,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let actionsSize = self.actionsStackNode.bounds.size var actionsPositionDeltaXDistance: CGFloat = 0.0 - if centerActionsHorizontally { + if case .center = actionsHorizontalAlignment { actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX } @@ -1116,7 +1120,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let actionsSize = self.actionsStackNode.bounds.size var actionsPositionDeltaXDistance: CGFloat = 0.0 - if centerActionsHorizontally { + if case .center = actionsHorizontalAlignment { actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX } let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index 319ca3f95e..cb99cd36d5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -99,7 +99,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo let keepInPlace: Bool = false let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private weak var chatNode: ChatControllerNode? private let engine: TelegramEngine @@ -174,7 +174,7 @@ final class ChatMessageNavigationButtonContextExtractedContentSource: ContextExt let keepInPlace: Bool = false let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private weak var chatNode: ChatControllerNode? private let contentNode: ContextExtractedContentContainingNode diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index c44d3945c9..244eed07ea 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -6,6 +6,7 @@ import TextFormat import UIKit import AppBundle import TelegramStringFormatting +import ContextUI enum PeerInfoScreenLabeledValueTextColor { case primary @@ -29,10 +30,11 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { let textColor: PeerInfoScreenLabeledValueTextColor let textBehavior: PeerInfoScreenLabeledValueTextBehavior let icon: PeerInfoScreenLabeledValueIcon? - let action: (() -> Void)? + let action: ((ASDisplayNode) -> Void)? let longTapAction: ((ASDisplayNode) -> Void)? let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? let iconAction: (() -> Void)? + let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let requestLayout: () -> Void init( @@ -43,10 +45,11 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { textColor: PeerInfoScreenLabeledValueTextColor = .primary, textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine, icon: PeerInfoScreenLabeledValueIcon? = nil, - action: (() -> Void)?, + action: ((ASDisplayNode) -> Void)?, longTapAction: ((ASDisplayNode) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, iconAction: (() -> Void)? = nil, + contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, requestLayout: @escaping () -> Void ) { self.id = id @@ -60,6 +63,7 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { self.longTapAction = longTapAction self.linkItemAction = linkItemAction self.iconAction = iconAction + self.contextAction = contextAction self.requestLayout = requestLayout } @@ -85,6 +89,14 @@ private func generateExpandBackground(size: CGSize, color: UIColor) -> UIImage? } private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { + private let containerNode: ContextControllerSourceNode + private let contextSourceNode: ContextExtractedContentContainingNode + + private let extractedBackgroundImageNode: ASImageNode + + private var extractedRect: CGRect? + private var nonExtractedRect: CGRect? + private let selectionNode: PeerInfoScreenSelectableBackgroundNode private let maskNode: ASImageNode private let labelNode: ImmediateTextNode @@ -111,6 +123,14 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { override init() { var bringToFrontForHighlightImpl: (() -> Void)? + + self.contextSourceNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + + self.extractedBackgroundImageNode = ASImageNode() + self.extractedBackgroundImageNode.displaysAsynchronously = false + self.extractedBackgroundImageNode.alpha = 0.0 + self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() }) self.selectionNode.isUserInteractionEnabled = false @@ -161,17 +181,25 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.addSubnode(self.bottomSeparatorNode) self.addSubnode(self.selectionNode) + + self.containerNode.addSubnode(self.contextSourceNode) + self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode + self.addSubnode(self.containerNode) + self.addSubnode(self.maskNode) - self.addSubnode(self.labelNode) - self.addSubnode(self.textNode) - self.addSubnode(self.additionalTextNode) - self.addSubnode(self.expandBackgroundNode) - self.addSubnode(self.expandNode) - self.addSubnode(self.expandButonNode) + self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.iconButtonNode) + self.contextSourceNode.contentNode.addSubnode(self.labelNode) + self.contextSourceNode.contentNode.addSubnode(self.textNode) + self.contextSourceNode.contentNode.addSubnode(self.additionalTextNode) + + self.contextSourceNode.contentNode.addSubnode(self.expandBackgroundNode) + self.contextSourceNode.contentNode.addSubnode(self.expandNode) + self.contextSourceNode.contentNode.addSubnode(self.expandButonNode) + + self.contextSourceNode.contentNode.addSubnode(self.iconNode) + self.contextSourceNode.contentNode.addSubnode(self.iconButtonNode) self.addSubnode(self.activateArea) @@ -200,6 +228,35 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { } } } + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else { + gesture.cancel() + return + } + contextAction(strongSelf.contextSourceNode, gesture, nil) + } + + self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self, let theme = strongSelf.theme else { + return + } + + if isExtracted { + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: theme.list.plainBackgroundColor) + } + + if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { + let rect = isExtracted ? extractedRect : nonExtractedRect + transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect) + } + + transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in + if !isExtracted { + self?.extractedBackgroundImageNode.image = nil + } + }) + } } @objc private func expandPressed() { @@ -260,7 +317,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { } else if case .longTap = gesture { item.longTapAction?(self) } else if case .tap = gesture { - item.action?() + item.action?(self.contextSourceNode) } } default: @@ -280,8 +337,16 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.item = item self.theme = presentationData.theme - self.selectionNode.pressed = item.action - + if let action = item.action { + self.selectionNode.pressed = { [weak self] in + if let strongSelf = self { + action(strongSelf.contextSourceNode) + } + } + } else { + self.selectionNode.pressed = nil + } + let sideInset: CGFloat = 16.0 + safeInsets.left self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor @@ -460,6 +525,25 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.activateArea.accessibilityLabel = item.label self.activateArea.accessibilityValue = item.text + + let contentSize = CGSize(width: width, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.containerNode.isGestureEnabled = false + + let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: contentSize.width, height: contentSize.height)) + let extractedRect = nonExtractedRect + self.extractedRect = extractedRect + self.nonExtractedRect = nonExtractedRect + + if self.contextSourceNode.isExtractedToContextPreview { + self.extractedBackgroundImageNode.frame = extractedRect + } else { + self.extractedBackgroundImageNode.frame = nonExtractedRect + } + self.contextSourceNode.contentRect = extractedRect + return height } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index ce6d41eb5c..18206af031 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -462,7 +462,7 @@ private enum PeerInfoReportType { private final class PeerInfoInteraction { let openChat: () -> Void let openUsername: (String) -> Void - let openPhone: (String) -> Void + let openPhone: (String, ASDisplayNode, ContextGesture?) -> Void let editingOpenNotificationSettings: () -> Void let editingOpenSoundSettings: () -> Void let editingToggleShowMessageText: (Bool) -> Void @@ -505,7 +505,7 @@ private final class PeerInfoInteraction { init( openUsername: @escaping (String) -> Void, - openPhone: @escaping (String) -> Void, + openPhone: @escaping (String, ASDisplayNode, ContextGesture?) -> Void, editingOpenNotificationSettings: @escaping () -> Void, editingOpenSoundSettings: @escaping () -> Void, editingToggleShowMessageText: @escaping (Bool) -> Void, @@ -942,10 +942,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if let phone = user.phone { let formattedPhone = formatPhoneNumber(phone) - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: presentationData.strings.ContactInfo_PhoneLabelMobile, text: formattedPhone, textColor: .accent, action: { - interaction.openPhone(phone) - }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.phone(formattedPhone), sourceNode) + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: presentationData.strings.ContactInfo_PhoneLabelMobile, text: formattedPhone, textColor: .accent, action: { node in + interaction.openPhone(phone, node, nil) + }, longTapAction: nil, contextAction: { node, gesture, _ in + interaction.openPhone(phone, node, gesture) }, requestLayout: { interaction.requestLayout(false) })) @@ -959,7 +959,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese additionalText: nil, //presentationData.strings.Profile_AdditionalUsernames("@username1, @username2, @username3, @username4"), textColor: .accent, icon: .qrCode, - action: { + action: { _ in interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) @@ -1076,7 +1076,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } if let username = channel.username { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, icon: .qrCode, action: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, icon: .qrCode, action: { _ in interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) @@ -1812,8 +1812,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate openUsername: { [weak self] value in self?.openUsername(value: value) }, - openPhone: { [weak self] value in - self?.openPhone(value: value) + openPhone: { [weak self] value, node, gesture in + self?.openPhone(value: value, node: node, gesture: gesture) }, editingOpenNotificationSettings: { [weak self] in self?.editingOpenNotificationSettings() @@ -4892,39 +4892,110 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } - private func openPhone(value: String) { + private func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + let _ = (getUserPeer(engine: self.context.engine, peerId: self.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formatPhoneNumber(value) == formatPhoneNumber(peerPhoneNumber) { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - let dismissAction: () -> Void = { [weak actionSheet] in - actionSheet?.dismissAnimated() +// if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formatPhoneNumber(value) == formatPhoneNumber(peerPhoneNumber) { +// let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) +// let dismissAction: () -> Void = { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// } +// actionSheet.setItemGroups([ +// ActionSheetItemGroup(items: [ +// ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: { +// dismissAction() +// self?.requestCall(isVideo: false) +// }), +// ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: { +// dismissAction() +// +// guard let strongSelf = self else { +// return +// } +// strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(value).replacingOccurrences(of: " ", with: ""))") +// }), +// ]), +// ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })]) +// ]) +// strongSelf.view.endEditing(true) +// strongSelf.controller?.present(actionSheet, in: .window(.root)) +// } else { +// strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(value).replacingOccurrences(of: " ", with: ""))") +// } +// + let presentationData = strongSelf.presentationData + + let telegramCallAction: (Bool) -> Void = { [weak self] isVideo in + guard let strongSelf = self else { + return + } + strongSelf.requestCall(isVideo: isVideo) + } + + let phoneCallAction = { [weak self] in + guard let strongSelf = self else { + return } - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: { - dismissAction() - self?.requestCall(isVideo: false) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: { - dismissAction() - - guard let strongSelf = self else { - return - } - strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(value).replacingOccurrences(of: " ", with: ""))") - }), - ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - strongSelf.view.endEditing(true) - strongSelf.controller?.present(actionSheet, in: .window(.root)) - } else { strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(value).replacingOccurrences(of: " ", with: ""))") } + + let copyAction = { [weak self] in + guard let strongSelf = self else { + return + } + UIPasteboard.general.string = formatPhoneNumber(value) + + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formatPhoneNumber(value) == formatPhoneNumber(peerPhoneNumber) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + telegramCallAction(false) + } + }))) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramVideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + telegramCallAction(true) + } + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: ""), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + phoneCallAction() + } + }))) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + copyAction() + } + }))) + } else { + items = [ + .action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: ""), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + phoneCallAction() + } + })), + .action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss { + copyAction() + } + })), + ] + } + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) +// contextController.useComplexItemsTransitionAnimation = true + strongSelf.controller?.present(contextController, in: .window(.root)) }) } @@ -8516,7 +8587,7 @@ private final class SettingsTabBarContextExtractedContentSource: ContextExtracte let keepInPlace: Bool = true let ignoreContentTouches: Bool = true let blurBackground: Bool = true - let centerActionsHorizontally: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center private let controller: ViewController private let sourceNode: ContextExtractedContentContainingNode @@ -8814,6 +8885,28 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten } } +private final class PeerInfoContextExtractedContentSource: ContextExtractedContentSource { + var keepInPlace: Bool = false + let ignoreContentTouches: Bool = true + let blurBackground: Bool = true + + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .right + + private let sourceNode: ContextExtractedContentContainingNode + + init(sourceNode: ContextExtractedContentContainingNode) { + self.sourceNode = sourceNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController private let sourceNode: ContextReferenceContentNode