diff --git a/Telegram/BUILD b/Telegram/BUILD index fa7bea15d5..03a0109fe6 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1885,6 +1885,8 @@ plist_fragment( CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + """.format( telegram_bundle_id = telegram_bundle_id, ) diff --git a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift index e6e160558c..61c54f4826 100644 --- a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift +++ b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift @@ -73,9 +73,9 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable { return lhs.index < rhs.index } - func item(context: AccountContext, mode: HorizontalPeerItemMode, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool) -> ListViewItem { - return HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: mode, context: context, peer: self.peer, presence: self.presence, unreadBadge: self.unreadBadge, action: peerSelected, contextAction: { peer, node, gesture in - peerContextAction(peer, node, gesture) + func item(context: AccountContext, mode: HorizontalPeerItemMode, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool) -> ListViewItem { + return HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: mode, context: context, peer: self.peer, presence: self.presence, unreadBadge: self.unreadBadge, action: peerSelected, contextAction: { peer, node, gesture, location in + peerContextAction(peer, node, gesture, location) }, isPeerSelected: isPeerSelected, customWidth: self.itemCustomWidth) } } @@ -88,7 +88,7 @@ private struct ChatListSearchRecentNodeTransition { let animated: Bool } -private func preparedRecentPeersTransition(context: AccountContext, mode: HorizontalPeerItemMode, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false, from fromEntries: [ChatListSearchRecentPeersEntry], to toEntries: [ChatListSearchRecentPeersEntry], firstTime: Bool, animated: Bool) -> ChatListSearchRecentNodeTransition { +private func preparedRecentPeersTransition(context: AccountContext, mode: HorizontalPeerItemMode, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false, from fromEntries: [ChatListSearchRecentPeersEntry], to toEntries: [ChatListSearchRecentPeersEntry], firstTime: Bool, animated: Bool) -> ChatListSearchRecentNodeTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -107,7 +107,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { private let share: Bool private let peerSelected: (EnginePeer) -> Void - private let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void + private let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void private let isPeerSelected: (EnginePeer.Id) -> Bool private let disposable = MetaDisposable() @@ -122,7 +122,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { return self.ready.get() } - public init(context: AccountContext, theme: PresentationTheme, mode: HorizontalPeerItemMode, strings: PresentationStrings, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false) { + public init(context: AccountContext, theme: PresentationTheme, mode: HorizontalPeerItemMode, strings: PresentationStrings, peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false) { self.theme = theme self.strings = strings self.themeAndStringsPromise = Promise((self.theme, self.strings)) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 02ed9d2d8d..c5de228318 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1119,7 +1119,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self?.toolbarActionSelected(action: action) } - self.chatListDisplayNode.containerNode.activateChatPreview = { [weak self] item, node, gesture in + self.chatListDisplayNode.containerNode.activateChatPreview = { [weak self] item, node, gesture, location in guard let strongSelf = self else { gesture?.cancel() return @@ -1141,14 +1141,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) case let .peer(_, peer, _, _, _, _, _, _, _, promoInfo, _, _, _): - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) - chatController.canReadHistory.set(false) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let source: ContextContentSource + if let location = location { + source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) + } else { + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) + chatController.canReadHistory.set(false) + source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) + } + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } - self.chatListDisplayNode.peerContextAction = { [weak self] peer, source, node, gesture in + self.chatListDisplayNode.peerContextAction = { [weak self] peer, source, node, gesture, location in guard let strongSelf = self else { gesture?.cancel() return @@ -3415,3 +3422,17 @@ private final class ChatListHeaderBarContextExtractedContentSource: ContextExtra return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class ChatListContextLocationContentSource: ContextLocationContentSource { + private let controller: ViewController + private let location: CGPoint + + init(controller: ViewController, location: CGPoint) { + self.controller = controller + self.location = location + } + + func transitionInfo() -> ContextControllerLocationViewInfo? { + return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index ae0d5b8e8f..7a64838eb2 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -181,7 +181,7 @@ private final class ChatListShimmerNode: ASDisplayNode { let timestamp1: Int32 = 100000 let peers: [EnginePeer.Id: EnginePeer] = [:] let interaction = ChatListNodeInteraction(context: context, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in + }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -529,8 +529,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.listNode.contentScrollingEnded = { [weak self] listView in return self?.contentScrollingEnded?(listView) ?? false } - itemNode.listNode.activateChatPreview = { [weak self] item, sourceNode, gesture in - self?.activateChatPreview?(item, sourceNode, gesture) + itemNode.listNode.activateChatPreview = { [weak self] item, sourceNode, gesture, location in + self?.activateChatPreview?(item, sourceNode, gesture, location) } itemNode.listNode.addedVisibleChatsWithPeerIds = { [weak self] ids in self?.addedVisibleChatsWithPeerIds?(ids) @@ -577,7 +577,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? var contentScrollingEnded: ((ListView) -> Bool)? - var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)? + var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var didBeginSelectingChats: (() -> Void)? var displayFilterLimit: (() -> Void)? @@ -1088,7 +1088,7 @@ final class ChatListControllerNode: ASDisplayNode { var requestOpenRecentPeerOptions: ((EnginePeer) -> Void)? var requestOpenMessageFromSearch: ((EnginePeer, EngineMessage.Id, Bool) -> Void)? var requestAddContact: ((String) -> Void)? - var peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)? + var peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var dismissSelfIfCompletedPresentation: (() -> Void)? var isEmptyUpdated: ((Bool) -> Void)? var emptyListAction: (() -> Void)? diff --git a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift index fd1bc9b262..400be7eb22 100644 --- a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift @@ -15,11 +15,11 @@ class ChatListRecentPeersListItem: ListViewItem { let context: AccountContext let peers: [EnginePeer] let peerSelected: (EnginePeer) -> Void - let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void + let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void let header: ListViewItemHeader? - init(theme: PresentationTheme, strings: PresentationStrings, context: AccountContext, peers: [EnginePeer], peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, context: AccountContext, peers: [EnginePeer], peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void) { self.theme = theme self.strings = strings self.context = context @@ -122,8 +122,8 @@ class ChatListRecentPeersListItemNode: ListViewItemNode { } else { peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list(compact: false), strings: item.strings, peerSelected: { peer in self?.item?.peerSelected(peer) - }, peerContextAction: { peer, node, gesture in - self?.item?.peerContextAction(peer, node, gesture) + }, peerContextAction: { peer, node, gesture, location in + self?.item?.peerContextAction(peer, node, gesture, location) }, isPeerSelected: { _ in return false }) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 1c68ea26ba..420d380b1f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -49,12 +49,12 @@ final class ChatListSearchInteraction { let toggleMessageSelection: (EngineMessage.Id, Bool) -> Void let messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void) let mediaMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) - let peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)? + let peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let present: (ViewController, Any?) -> Void let dismissInput: () -> Void let getSelectedMessageIds: () -> Set? - init(openPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set?) { + init(openPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set?) { self.openPeer = openPeer self.openDisabledPeer = openDisabledPeer self.openMessage = openMessage @@ -129,7 +129,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private var validLayout: (ContainerViewLayout, CGFloat)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter, groupId: EngineChatList.Group, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter, groupId: EngineChatList.Group, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { self.context = context self.peersFilter = filter self.groupId = groupId @@ -225,8 +225,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self?.messageContextAction(message, node: node, rect: rect, gesture: gesture, paneKey: paneKey, downloadResource: downloadResource) }, mediaMessageContextAction: { [weak self] message, node, rect, gesture in self?.mediaMessageContextAction(message, node: node, rect: rect, gesture: gesture) - }, peerContextAction: { peer, source, node, gesture in - peerContextAction?(peer, source, node, gesture) + }, peerContextAction: { peer, source, node, gesture, location in + peerContextAction?(peer, source, node, gesture, location) }, present: { c, a in present(c, a) }, dismissInput: { [weak self] in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 206e4f3eb1..e7abed7b33 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -87,14 +87,14 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ListViewItem { + func item(context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ListViewItem { switch self { case let .topPeers(peers, theme, strings): return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in peerSelected(peer) - }, peerContextAction: { peer, node, gesture in + }, peerContextAction: { peer, node, gesture, location in if let peerContextAction = peerContextAction { - peerContextAction(peer, .recentPeers, node, gesture) + peerContextAction(peer, .recentPeers, node, gesture, location) } else { gesture?.cancel() } @@ -204,9 +204,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable { disabledPeerSelected(EnginePeer(chatPeer)) } }, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in - return { node, gesture in + return { node, gesture, location in if let chatPeer = peer.peer.peers[peer.peer.peerId] { - peerContextAction(EnginePeer(chatPeer), .recentSearch, node, gesture) + peerContextAction(EnginePeer(chatPeer), .recentSearch, node, gesture, location) } else { gesture?.cancel() } @@ -418,7 +418,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { switch self { case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder): let primaryPeer: EnginePeer @@ -488,9 +488,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { interaction.peerSelected(peer, nil, nil) } }, contextAction: peerContextAction.flatMap { peerContextAction in - return { node, gesture in + return { node, gesture, location in if let chatPeer = chatPeer, chatPeer.id.namespace != Namespaces.Peer.SecretChat { - peerContextAction(chatPeer, .search(nil), node, gesture) + peerContextAction(chatPeer, .search(nil), node, gesture, location) } else { gesture?.cancel() } @@ -575,9 +575,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { interaction.peerSelected(peer, nil, nil) } }, contextAction: peerContextAction.flatMap { peerContextAction in - return { node, gesture in + return { node, gesture, location in if let chatPeer = chatPeer, chatPeer.id.namespace != Namespaces.Peer.SecretChat { - peerContextAction(chatPeer, .search(nil), node, gesture) + peerContextAction(chatPeer, .search(nil), node, gesture, location) } else { gesture?.cancel() } @@ -638,8 +638,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in interaction.peerSelected(EnginePeer(peer.peer), nil, nil) }, contextAction: peerContextAction.flatMap { peerContextAction in - return { node, gesture in - peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture) + return { node, gesture, location in + peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) } }) case let .message(message, peer, readState, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused): @@ -710,7 +710,7 @@ public struct ChatListSearchContainerTransition { } } -private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ChatListSearchContainerRecentTransition { +private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], context: AccountContext, presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, peerSelected: @escaping (EnginePeer) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void) -> ChatListSearchContainerRecentTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -720,7 +720,7 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -1720,7 +1720,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { }, hidePsa: { _ in - }, activateChatPreview: { item, node, gesture in + }, activateChatPreview: { item, node, gesture, location in guard let peerContextAction = interaction.peerContextAction else { gesture?.cancel() return @@ -1728,7 +1728,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { switch item.content { case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): if let peer = peer.peer, let message = messages.first { - peerContextAction(peer, .search(message.id), node, gesture) + peerContextAction(peer, .search(message.id), node, gesture, location) } case .groupReference: gesture?.cancel() @@ -1879,8 +1879,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil) let firstTime = previousEntries == nil - var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in - interaction.peerContextAction?(message, node, rect, gesture) + var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in + interaction.peerContextAction?(message, node, rect, gesture, location) }, toggleExpandLocalResults: { guard let strongSelf = self else { return @@ -2042,9 +2042,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self?.recentListNode.clearHighlightAnimated(true) }, disabledPeerSelected: { peer in interaction.openDisabledPeer(peer) - }, peerContextAction: { peer, source, node, gesture in + }, peerContextAction: { peer, source, node, gesture, location in if let peerContextAction = interaction.peerContextAction { - peerContextAction(peer, source, node, gesture) + peerContextAction(peer, source, node, gesture, location) } else { gesture?.cancel() } @@ -2925,7 +2925,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer1.id] = peer1 let interaction = ChatListNodeInteraction(context: context, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in + }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 564b0569a5..5f27925810 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -163,7 +163,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { self.interaction.groupSelected(groupId) } } - + static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool) { var first = false var last = false @@ -734,7 +734,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let strongSelf = self, let item = strongSelf.item else { return } - item.interaction.activateChatPreview(item, strongSelf.contextContainer, gesture) + item.interaction.activateChatPreview(item, strongSelf.contextContainer, gesture, nil) } } @@ -743,6 +743,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.playbackStartDisposable.dispose() } + override func secondaryAction(at point: CGPoint) { + guard let item = self.item else { + return + } + item.interaction.activateChatPreview(item, self.contextContainer, nil, point) + } + func setupItem(item: ChatListItem, synchronousLoads: Bool) { let previousItem = self.item self.item = item diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index ba7c393edf..658e20eb15 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -72,7 +72,7 @@ public final class ChatListNodeInteraction { let togglePeerMarkedUnread: (EnginePeer.Id, Bool) -> Void let toggleArchivedFolderHiddenByDefault: () -> Void let hidePsa: (EnginePeer.Id) -> Void - let activateChatPreview: (ChatListItem, ASDisplayNode, ContextGesture?) -> Void + let activateChatPreview: (ChatListItem, ASDisplayNode, ContextGesture?, CGPoint?) -> Void let present: (ViewController) -> Void public var searchTextHighightState: String? @@ -81,7 +81,7 @@ public final class ChatListNodeInteraction { let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer - public init(context: AccountContext, activateSearch: @escaping () -> Void, peerSelected: @escaping (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, togglePeerSelected: @escaping (EnginePeer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (EngineChatList.Group) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setItemPinned: @escaping (EngineChatList.PinnedItem.Id, Bool) -> Void, setPeerMuted: @escaping (EnginePeer.Id, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (EnginePeer.Id) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { + public init(context: AccountContext, activateSearch: @escaping () -> Void, peerSelected: @escaping (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, togglePeerSelected: @escaping (EnginePeer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (EngineChatList.Group) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setItemPinned: @escaping (EngineChatList.PinnedItem.Id, Bool) -> Void, setPeerMuted: @escaping (EnginePeer.Id, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (EnginePeer.Id) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, present: @escaping (ViewController) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.disabledPeerSelected = disabledPeerSelected @@ -631,7 +631,7 @@ public final class ChatListNode: ListView { public var push: ((ViewController) -> Void)? public var toggleArchivedFolderHiddenByDefault: (() -> Void)? public var hidePsa: ((EnginePeer.Id) -> Void)? - public var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)? + public var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? private var theme: PresentationTheme @@ -917,12 +917,12 @@ public final class ChatListNode: ListView { self?.toggleArchivedFolderHiddenByDefault?() }, hidePsa: { [weak self] id in self?.hidePsa?(id) - }, activateChatPreview: { [weak self] item, node, gesture in + }, activateChatPreview: { [weak self] item, node, gesture, location in guard let strongSelf = self else { return } if let activateChatPreview = strongSelf.activateChatPreview { - activateChatPreview(item, node, gesture) + activateChatPreview(item, node, gesture, location) } else { gesture?.cancel() } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 6efb4701dc..279b65aeec 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -99,11 +99,11 @@ private final class ContactListNodeInteraction { fileprivate let authorize: () -> Void fileprivate let suppressWarning: () -> Void fileprivate let openPeer: (ContactListPeer, ContactListAction) -> Void - fileprivate let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? + fileprivate let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let itemHighlighting = ContactItemHighlighting() - init(activateSearch: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) { + init(activateSearch: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) { self.activateSearch = activateSearch self.authorize = authorize self.suppressWarning = suppressWarning @@ -211,13 +211,13 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if isSearch { status = .none } - var itemContextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + var itemContextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? if isContextActionEnabled, let contextAction = interaction.contextAction { - itemContextAction = { node, gesture in + itemContextAction = { node, gesture, location in switch itemPeer { case let .peer(peer, _): if let peer = peer { - contextAction(peer, node, gesture) + contextAction(peer, node, gesture, location) } case .deviceContact: break @@ -866,7 +866,7 @@ public final class ContactListNode: ASDisplayNode { public var openPeer: ((ContactListPeer, ContactListAction) -> Void)? public var openPrivacyPolicy: (() -> Void)? public var suppressPermissionWarning: (() -> Void)? - private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? + private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) private let disposable = MetaDisposable() @@ -880,7 +880,7 @@ public final class ContactListNode: ASDisplayNode { public var multipleSelection = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentation: Signal, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentation: Signal, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { self.context = context self.filters = filters self.displayPermissionPlaceholder = displayPermissionPlaceholder diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 109565d468..95800a0b31 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -88,10 +88,10 @@ final class ContactsControllerNode: ASDisplayNode { } } - var contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? + var contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? - self.contactListNode = ContactListNode(context: context, presentation: presentation, displaySortOptions: true, contextAction: { peer, node, gesture in - contextAction?(peer, node, gesture) + self.contactListNode = ContactListNode(context: context, presentation: presentation, displaySortOptions: true, contextAction: { peer, node, gesture, location in + contextAction?(peer, node, gesture, location) }) super.init() @@ -130,8 +130,8 @@ final class ContactsControllerNode: ASDisplayNode { } } - contextAction = { [weak self] peer, node, gesture in - self?.contextAction(peer: peer, node: node, gesture: gesture) + contextAction = { [weak self] peer, node, gesture, location in + self?.contextAction(peer: peer, node: node, gesture: gesture, location: location) } } @@ -170,7 +170,7 @@ final class ContactsControllerNode: ASDisplayNode { self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size) } - private func contextAction(peer: EnginePeer, node: ASDisplayNode, gesture: ContextGesture?) { + private func contextAction(peer: EnginePeer, node: ASDisplayNode?, gesture: ContextGesture?, location: CGPoint?) { guard let contactsController = self.controller else { return } @@ -193,8 +193,8 @@ final class ContactsControllerNode: ASDisplayNode { if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch { requestOpenPeerFromSearch(peer) } - }, contextAction: { [weak self] peer, node, gesture in - self?.contextAction(peer: peer, node: node, gesture: gesture) + }, contextAction: { [weak self] peer, node, gesture, location in + self?.contextAction(peer: peer, node: node, gesture: gesture, location: location) }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 5336adfb33..c01bd6b402 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -121,7 +121,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ListViewItem { switch self { case let .addContact(theme, strings, phoneNumber): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -162,8 +162,8 @@ private enum ContactListSearchEntry: Comparable, Identifiable { openPeer(peer) }, contextAction: contextAction.flatMap { contextAction in return nativePeer.flatMap { nativePeer in - return { node, gesture in - contextAction(nativePeer, node, gesture) + return { node, gesture, location in + contextAction(nativePeer, node, gesture, location) } } }) @@ -180,7 +180,7 @@ struct ContactListSearchContainerTransition { let query: String } -private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition { +private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ContactListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -206,7 +206,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo private let context: AccountContext private let addContact: ((String) -> Void)? private let openPeer: (ContactListPeer) -> Void - private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? + private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? private let dimNode: ASDisplayNode public let listNode: ListView @@ -229,7 +229,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo return true } - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) { self.context = context self.addContact = addContact self.openPeer = openPeer diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index ea842498af..050eb5789e 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -148,7 +148,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? let deletePeer: ((EnginePeer.Id) -> Void)? let itemHighlighting: ContactItemHighlighting? - let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let arrowAction: (() -> Void)? public let selectable: Bool @@ -157,7 +157,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, selectionPosition: ContactsPeerItemSelectionPosition = .right, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: SortIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil, deletePeer: ((EnginePeer.Id) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) { + public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, selectionPosition: ContactsPeerItemSelectionPosition = .right, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: SortIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil, deletePeer: ((EnginePeer.Id) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) { self.presentationData = presentationData self.style = style self.sectionId = sectionId @@ -427,7 +427,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { gesture.cancel() return } - contextAction(strongSelf.containerNode, gesture) + contextAction(strongSelf.containerNode, gesture, nil) } self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in @@ -453,6 +453,13 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } + public override func secondaryAction(at point: CGPoint) { + guard let item = self.item, let contextAction = item.contextAction else { + return + } + contextAction(self.containerNode, nil, point) + } + override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let (item, _, _, _, _, _) = self.layoutParams { let (first, last, firstWithHeader) = ContactsPeerItem.mergeType(item: item, previousItem: previousItem, nextItem: nextItem) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 5e655079d0..98d5d21b73 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -193,7 +193,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi fileprivate var dismissedForCancel: (() -> Void)? private let getController: () -> ContextControllerProtocol? private weak var gesture: ContextGesture? - + private var didSetItemsReady = false let itemsReady = Promise() let contentReady = Promise() @@ -371,7 +371,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if strongSelf.didMoveFromInitialGesturePoint { if let presentationNode = strongSelf.presentationNode { let presentationPoint = strongSelf.view.convert(localPoint, to: presentationNode.view) - presentationNode.highlightGestureMoved(location: presentationPoint) + presentationNode.highlightGestureMoved(location: presentationPoint, hover: false) } else { let actionPoint = strongSelf.view.convert(localPoint, to: strongSelf.actionsContainerNode.view) let actionNode = strongSelf.actionsContainerNode.actionNode(at: actionPoint) @@ -445,7 +445,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if strongSelf.didMoveFromInitialGesturePoint { if let presentationNode = strongSelf.presentationNode { let presentationPoint = strongSelf.view.convert(localPoint, to: presentationNode.view) - presentationNode.highlightGestureMoved(location: presentationPoint) + presentationNode.highlightGestureMoved(location: presentationPoint, hover: false) } else { let actionPoint = strongSelf.view.convert(localPoint, to: strongSelf.actionsContainerNode.view) var actionNode = strongSelf.actionsContainerNode.actionNode(at: actionPoint) @@ -504,7 +504,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } switch source { - case .reference, .extracted: + case .location, .reference, .extracted: self.contentReady.set(.single(true)) case let .controller(source): self.contentReady.set(source.controller.ready.get()) @@ -538,6 +538,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi super.didLoad() self.dismissNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapped))) + + if #available(iOS 13.0, *) { + self.view.addGestureRecognizer(UIHoverGestureRecognizer(target: self, action: #selector(self.hoverGesture(_:)))) + } } @objc private func dimNodeTapped() { @@ -548,8 +552,93 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.beginDismiss(.default) } + @available(iOS 13.0, *) + @objc private func hoverGesture(_ gestureRecognizer: UIHoverGestureRecognizer) { + guard self.didCompleteAnimationIn else { + return + } + + let localPoint = gestureRecognizer.location(in: self.view) + + switch gestureRecognizer.state { + case .changed: + if let presentationNode = self.presentationNode { + let presentationPoint = self.view.convert(localPoint, to: presentationNode.view) + presentationNode.highlightGestureMoved(location: presentationPoint, hover: true) + } else { + let actionPoint = self.view.convert(localPoint, to: self.actionsContainerNode.view) + let actionNode = self.actionsContainerNode.actionNode(at: actionPoint) + if self.highlightedActionNode !== actionNode { + self.highlightedActionNode?.setIsHighlighted(false) + self.highlightedActionNode = actionNode + if let actionNode = actionNode { + actionNode.setIsHighlighted(true) + } + } + + if let reactionContextNode = self.reactionContextNode { + let reactionPoint = self.view.convert(localPoint, to: reactionContextNode.view) + let highlightedReaction = reactionContextNode.reaction(at: reactionPoint)?.reaction + if self.highlightedReaction?.rawValue != highlightedReaction?.rawValue { + self.highlightedReaction = highlightedReaction + self.hapticFeedback.tap() + } + } + } + case .ended, .cancelled: + if let presentationNode = self.presentationNode { + presentationNode.highlightGestureMoved(location: CGPoint(x: -1, y: -1), hover: true) + } else { + if let highlightedActionNode = self.highlightedActionNode { + self.highlightedActionNode = nil + highlightedActionNode.setIsHighlighted(false) + } + + if let _ = self.reactionContextNode { + self.highlightedReaction = nil + } + } + default: + break + } + } + private func initializeContent() { switch self.source { + case let .location(source): + let presentationNode = ContextControllerExtractedPresentationNode( + getController: { [weak self] in + return self?.getController() + }, + requestUpdate: { [weak self] transition in + guard let strongSelf = self else { + return + } + if let validLayout = strongSelf.validLayout { + strongSelf.updateLayout( + layout: validLayout, + transition: transition, + previousActionsContainerNode: nil + ) + } + }, + requestDismiss: { [weak self] result in + guard let strongSelf = self else { + return + } + strongSelf.dismissedForCancel?() + strongSelf.beginDismiss(result) + }, + requestAnimateOut: { [weak self] result, completion in + guard let strongSelf = self else { + return + } + strongSelf.animateOut(result: result, completion: completion) + }, + source: .location(source) + ) + self.presentationNode = presentationNode + self.addSubnode(presentationNode) case let .reference(source): if let controller = self.getController() as? ContextController, controller.workaroundUseLegacyImplementation { let transitionInfo = source.transitionInfo() @@ -701,7 +790,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } switch self.source { - case .reference: + case .location, .reference: break case .extracted: if let contentAreaInScreenSpace = self.contentAreaInScreenSpace, let maybeContentNode = self.contentContainerNode.contentNode, case .extracted = maybeContentNode { @@ -900,6 +989,53 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi var result = initialResult switch self.source { + case let .location(source): + let transitionInfo = source.transitionInfo() + if transitionInfo == nil { + result = .dismissWithoutContent + } + + switch result { + case let .custom(value): + switch value { + case let .animated(duration, curve): + transitionDuration = duration + transitionCurve = curve + default: + break + } + default: + break + } + + self.isUserInteractionEnabled = false + self.isAnimatingOut = true + + self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false) + + if !self.dimNode.isHidden { + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } else { + self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + } + + self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15 * animationDurationFactor, removeOnCompletion: false, completion: { _ in + completion() + }) + self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) + + let animateOutToItem: Bool + switch result { + case .default, .custom: + animateOutToItem = true + case .dismissWithoutContent: + animateOutToItem = false + } + + if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) + self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true) + } case let .reference(source): guard let maybeContentNode = self.contentContainerNode.contentNode, case let .reference(referenceView) = maybeContentNode else { return @@ -2071,6 +2207,30 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } +public final class ContextControllerLocationViewInfo { + public let location: CGPoint + public let contentAreaInScreenSpace: CGRect + public let insets: UIEdgeInsets + + public init(location: CGPoint, contentAreaInScreenSpace: CGRect, insets: UIEdgeInsets = UIEdgeInsets()) { + self.location = location + self.contentAreaInScreenSpace = contentAreaInScreenSpace + self.insets = insets + } +} + +public protocol ContextLocationContentSource: AnyObject { + var shouldBeDismissed: Signal { get } + + func transitionInfo() -> ContextControllerLocationViewInfo? +} + +public extension ContextLocationContentSource { + var shouldBeDismissed: Signal { + return .single(false) + } +} + public final class ContextControllerReferenceViewInfo { public let referenceView: UIView public let contentAreaInScreenSpace: CGRect @@ -2171,6 +2331,7 @@ public protocol ContextControllerContentSource: AnyObject { } public enum ContextContentSource { + case location(ContextLocationContentSource) case reference(ContextReferenceContentSource) case extracted(ContextExtractedContentSource) case controller(ContextControllerContentSource) @@ -2297,6 +2458,18 @@ public final class ContextController: ViewController, StandalonePresentableContr super.init(navigationBarPresentationData: nil) switch source { + case let .location(locationSource): + self.statusBar.statusBarStyle = .Ignore + + self.shouldBeDismissedDisposable = (locationSource.shouldBeDismissed + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.dismiss(result: .default, completion: {}) + }) case let .reference(referenceSource): self.statusBar.statusBarStyle = .Ignore diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index ee33474803..66d0b94623 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -111,6 +111,7 @@ private extension ContextControllerTakeViewInfo.ContainingItem { final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode, UIScrollViewDelegate { enum ContentSource { + case location(ContextLocationContentSource) case reference(ContextReferenceContentSource) case extracted(ContextExtractedContentSource) } @@ -298,11 +299,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } - func highlightGestureMoved(location: CGPoint) { + func highlightGestureMoved(location: CGPoint, hover: Bool) { self.actionsStackNode.highlightGestureMoved(location: self.view.convert(location, to: self.actionsStackNode.view)) if let reactionContextNode = self.reactionContextNode { - reactionContextNode.highlightGestureMoved(location: self.view.convert(location, to: reactionContextNode.view)) + reactionContextNode.highlightGestureMoved(location: self.view.convert(location, to: reactionContextNode.view), hover: hover) } } @@ -337,7 +338,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo private func getActionsStackPositionLock() -> CGFloat? { switch self.source { - case .reference: + case .location, .reference: return nil case .extracted: return self.actionsStackNode.view.convert(CGPoint(), to: self.view).y @@ -366,7 +367,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } switch self.source { - case .reference: + case .location, .reference: self.backgroundNode.updateColor( color: .clear, enableBlur: false, @@ -396,7 +397,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo contentNode = current } else { switch self.source { - case .reference: + case .location, .reference: contentNode = nil case let .extracted(source): guard let takeInfo = source.takeView() else { @@ -453,6 +454,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var contentRect: CGRect switch self.source { + case let .location(location): + if let transitionInfo = location.transitionInfo() { + contentRect = CGRect(origin: transitionInfo.location, size: CGSize(width: 1.0, height: 1.0)) + contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) + } else { + return + } case let .reference(reference): if let transitionInfo = reference.transitionInfo() { contentRect = convertFrame(transitionInfo.referenceView.bounds, from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0) @@ -478,7 +486,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let keepInPlace: Bool let centerActionsHorizontally: Bool switch self.source { - case .reference: + case .location, .reference: keepInPlace = true centerActionsHorizontally = false case let .extracted(source): @@ -505,7 +513,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let actionsStackPresentation: ContextControllerActionsStackNode.Presentation switch self.source { - case .reference: + case .location, .reference: actionsStackPresentation = .inline case .extracted: actionsStackPresentation = .modal @@ -578,11 +586,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo actionsFrame.origin.x = actionsEdgeInset } } else { - if contentRect.midX < layout.size.width / 2.0 { + 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 { switch self.source { - case .reference: + 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 @@ -794,6 +804,15 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let currentContentScreenFrame: CGRect switch self.source { + case let .location(location): + if let putBackInfo = location.transitionInfo() { + self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + + currentContentScreenFrame = CGRect(origin: putBackInfo.location, size: CGSize(width: 1.0, height: 1.0)) + } else { + return + } case let .reference(source): if let putBackInfo = source.transitionInfo() { self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) diff --git a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift index 54eb0fec80..95a0af80d9 100644 --- a/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerPresentationNode.swift @@ -28,7 +28,7 @@ protocol ContextControllerPresentationNode: ASDisplayNode { func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) func cancelReactionAnimation() - func highlightGestureMoved(location: CGPoint) + func highlightGestureMoved(location: CGPoint, hover: Bool) func highlightGestureFinished(performAction: Bool) func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index b482bc67ad..278de23218 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -4623,38 +4623,61 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } override open func touchesEnded(_ touches: Set, with event: UIEvent?) { + let isSecondary: Bool + if #available(iOS 13.4, *) { + isSecondary = event?.buttonMask == .secondary + } else { + isSecondary = false + } + if let selectionTouchLocation = self.selectionTouchLocation { let index = self.itemIndexAtPoint(selectionTouchLocation) - if index != self.highlightedItemIndex { - self.clearHighlightAnimated(false) - } - if let index = index { - if self.items[index].selectable { - self.highlightedItemIndex = index - for itemNode in self.itemNodes { - if itemNode.index == index { - if itemNode.canBeSelected { - if !itemNode.isLayerBacked { - self.reorderItemNodeToFront(itemNode) - for (_, headerNode) in self.itemHeaderNodes { - self.reorderHeaderNodeToFront(headerNode) - } - } - let itemNodeFrame = itemNode.frame - itemNode.setHighlighted(true, at: selectionTouchLocation.offsetBy(dx: -itemNodeFrame.minX, dy: -itemNodeFrame.minY), animated: false) - } else { - self.highlightedItemIndex = nil - itemNode.tapped() + if isSecondary { + if let index = index { + if self.items[index].selectable { + self.highlightedItemIndex = index + for itemNode in self.itemNodes { + if itemNode.index == index { + itemNode.secondaryAction(at: selectionTouchLocation) + self.items[index].performSecondaryAction(listView: self) + break + } + } + } + } + } else { + if index != self.highlightedItemIndex { + self.clearHighlightAnimated(false) + } + + if let index = index { + if self.items[index].selectable { + self.highlightedItemIndex = index + for itemNode in self.itemNodes { + if itemNode.index == index { + if itemNode.canBeSelected { + if !itemNode.isLayerBacked { + self.reorderItemNodeToFront(itemNode) + for (_, headerNode) in self.itemHeaderNodes { + self.reorderHeaderNodeToFront(headerNode) + } + } + let itemNodeFrame = itemNode.frame + itemNode.setHighlighted(true, at: selectionTouchLocation.offsetBy(dx: -itemNodeFrame.minX, dy: -itemNodeFrame.minY), animated: false) + } else { + self.highlightedItemIndex = nil + itemNode.tapped() + } + break } - break } } } } } - - if let highlightedItemIndex = self.highlightedItemIndex { + + if !isSecondary, let highlightedItemIndex = self.highlightedItemIndex { for itemNode in self.itemNodes { if itemNode.index == highlightedItemIndex { itemNode.selected() diff --git a/submodules/Display/Source/ListViewItem.swift b/submodules/Display/Source/ListViewItem.swift index f99829ea00..4a20af1620 100644 --- a/submodules/Display/Source/ListViewItem.swift +++ b/submodules/Display/Source/ListViewItem.swift @@ -96,4 +96,7 @@ public extension ListViewItem { func selected(listView: ListView) { } + + func performSecondaryAction(listView: ListView) { + } } diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 54080485d9..ffee957d65 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -586,6 +586,9 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { open func selected() { } + open func secondaryAction(at point: CGPoint) { + } + open func isReorderable(at point: CGPoint) -> Bool { return false } diff --git a/submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift b/submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift index 42e6e2beb2..a6e37daf86 100644 --- a/submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift +++ b/submodules/Display/Source/TapLongTapOrDoubleTapGestureRecognizer.swift @@ -60,6 +60,7 @@ public enum TapLongTapOrDoubleTapGesture { case tap case doubleTap case longTap + case secondaryTap case hold } @@ -81,6 +82,8 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, public var tapActionAtPoint: ((CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction)? public var longTap: ((CGPoint, TapLongTapOrDoubleTapGestureRecognizer) -> Void)? + public var secondaryTap: ((CGPoint, TapLongTapOrDoubleTapGestureRecognizer) -> Void)? + private var recognizedLongTap: Bool = false public var externalUpdated: ((UIView?, CGPoint) -> Void)? public var externalEnded: (((UIView?, CGPoint)?) -> Void)? @@ -178,10 +181,17 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, self.touchCount += touches.count + let isSecondary: Bool + if #available(iOS 13.4, *) { + isSecondary = event.buttonMask == .secondary + } else { + isSecondary = false + } + if let touch = touches.first { let touchLocation = touch.location(in: self.view) - if self.highlightPoint != touchLocation { + if !isSecondary, self.highlightPoint != touchLocation { self.highlightPoint = touchLocation self.highlight?(touchLocation) } @@ -261,6 +271,13 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, self.touchCount -= touches.count + let isSecondary: Bool + if #available(iOS 13.4, *) { + isSecondary = event.buttonMask == .secondary + } else { + isSecondary = false + } + if self.highlightPoint != nil { self.highlightPoint = nil self.highlight?(nil) @@ -281,32 +298,37 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, } if self.tapCount == 1 { - var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap - if let tapActionAtPoint = self.tapActionAtPoint, let (touchLocation, _) = self.touchLocationAndTimestamp { - tapAction = tapActionAtPoint(touchLocation) - } - - switch tapAction { - case .waitForSingleTap, .keepWithSingleTap: - if let (touchLocation, _) = self.touchLocationAndTimestamp { - self.lastRecognizedGestureAndLocation = (.tap, touchLocation) - } - self.state = .ended - case .waitForDoubleTap: - self.state = .began - let timer = Timer(timeInterval: 0.16, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.tapEvent), userInfo: nil, repeats: false) - self.timer = timer - RunLoop.main.add(timer, forMode: .common) - case let .waitForHold(_, acceptTap): - if let (touchLocation, _) = self.touchLocationAndTimestamp, acceptTap { - if self.state != .began { + if isSecondary, let (touchLocation, _) = self.touchLocationAndTimestamp { + self.secondaryTap?(touchLocation, self) + self.state = .ended + } else { + var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap + if let tapActionAtPoint = self.tapActionAtPoint, let (touchLocation, _) = self.touchLocationAndTimestamp { + tapAction = tapActionAtPoint(touchLocation) + } + + switch tapAction { + case .waitForSingleTap, .keepWithSingleTap: + if let (touchLocation, _) = self.touchLocationAndTimestamp { self.lastRecognizedGestureAndLocation = (.tap, touchLocation) - self.state = .began } - } - self.state = .ended - case .fail: - self.state = .failed + self.state = .ended + case .waitForDoubleTap: + self.state = .began + let timer = Timer(timeInterval: 0.16, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.tapEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: .common) + case let .waitForHold(_, acceptTap): + if let (touchLocation, _) = self.touchLocationAndTimestamp, acceptTap { + if self.state != .began { + self.lastRecognizedGestureAndLocation = (.tap, touchLocation) + self.state = .began + } + } + self.state = .ended + case .fail: + self.state = .failed + } } } } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 1fc0704580..79582574cd 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -72,7 +72,7 @@ public final class HashtagSearchController: TelegramBaseController { }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { }, hidePsa: { _ in - }, activateChatPreview: { _, _, gesture in + }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift index 5051bbe78f..b35103ec43 100644 --- a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift +++ b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift @@ -25,13 +25,13 @@ public final class HorizontalPeerItem: ListViewItem { let context: AccountContext public let peer: EnginePeer let action: (EnginePeer) -> Void - let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? + let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let isPeerSelected: (EnginePeer.Id) -> Bool let customWidth: CGFloat? let presence: EnginePeer.Presence? let unreadBadge: (Int32, Bool)? - public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, context: AccountContext, peer: EnginePeer, presence: EnginePeer.Presence?, unreadBadge: (Int32, Bool)?, action: @escaping (EnginePeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, customWidth: CGFloat?) { + public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, context: AccountContext, peer: EnginePeer, presence: EnginePeer.Presence?, unreadBadge: (Int32, Bool)?, action: @escaping (EnginePeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, customWidth: CGFloat?) { self.theme = theme self.strings = strings self.mode = mode @@ -191,9 +191,9 @@ public final class HorizontalPeerItemNode: ListViewItemNode { strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false) if let contextAction = item.contextAction { - strongSelf.peerNode.contextAction = { [weak item] node, gesture in + strongSelf.peerNode.contextAction = { [weak item] node, gesture, location in if let item = item { - contextAction(item.peer, node, gesture) + contextAction(item.peer, node, gesture, location) } } } else { diff --git a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift index e4e46d604c..1f3c40f659 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift @@ -13,6 +13,10 @@ public final class ItemListRevealOptionsGestureRecognizer: UIPanGestureRecognize override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) + if #available(iOS 13.4, *) { + self.allowedScrollTypesMask = .continuous + } + self.maximumNumberOfTouches = 1 } @@ -161,9 +165,13 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if let recognizer = self.recognizer, gestureRecognizer == self.tapRecognizer { return abs(self.revealOffset) > 0.0 && !recognizer.validatedGesture - } else { - return true + } else if let recognizer = self.recognizer, gestureRecognizer == self.recognizer, recognizer.numberOfTouches == 0 { + let translation = recognizer.velocity(in: recognizer.view) + if abs(translation.y) > 4.0 && abs(translation.y) > abs(translation.x) * 2.5 { + return false + } } + return true } open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index 131e2b51fb..422af3781e 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -826,7 +826,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { item.interaction.openUrl(url, false, true, nil) } } - case .hold, .doubleTap: + case .hold, .doubleTap, .secondaryTap: break } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index af13929f5a..508b871d78 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -85,6 +85,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private var longPressTimer: SwiftSignalKit.Timer? private var highlightedReaction: ReactionItem.Reaction? + private var highlightedByHover = false private var didTriggerExpandedReaction: Bool = false private var continuousHaptic: Any? private var validLayout: (CGSize, UIEdgeInsets, CGRect)? @@ -467,7 +468,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { size: visualBackgroundFrame.size, cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX, isLeftAligned: isLeftAligned, - isMinimized: self.highlightedReaction != nil, + isMinimized: self.highlightedReaction != nil && !self.highlightedByHover, transition: transition ) @@ -846,14 +847,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - public func highlightGestureMoved(location: CGPoint) { + public func highlightGestureMoved(location: CGPoint, hover: Bool) { let highlightedReaction = self.previewReaction(at: location)?.reaction if self.highlightedReaction != highlightedReaction { self.highlightedReaction = highlightedReaction - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() + self.highlightedByHover = hover && highlightedReaction != nil + + if !hover { + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + self.hapticFeedback?.tap() } - self.hapticFeedback?.tap() if let (size, insets, anchorRect) = self.validLayout { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true) diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index c590dfa764..318e21e449 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -74,7 +74,7 @@ public final class SelectablePeerNode: ASDisplayNode { private let textNode: ASTextNode public var toggleSelection: (() -> Void)? - public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? { + public var contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? { didSet { self.contextContainer.isGestureEnabled = self.contextAction != nil } @@ -133,7 +133,7 @@ public final class SelectablePeerNode: ASDisplayNode { gesture.cancel() return } - contextAction(strongSelf.contextContainer, gesture) + contextAction(strongSelf.contextContainer, gesture, nil) } } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 0e7ceca482..fb04805e2a 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -212,7 +212,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let interaction = ChatListNodeInteraction(context: self.context, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in - }, activateChatPreview: { _, _, gesture in + }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 309f10eed5..3bd404d4b4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -832,7 +832,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let interaction = ChatListNodeInteraction(context: self.context, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in - }, activateChatPreview: { _, _, gesture in + }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index b543c852c4..5923c11d0e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -356,7 +356,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let interaction = ChatListNodeInteraction(context: self.context, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in - }, activateChatPreview: { _, _, gesture in + }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift index f96f74110e..f1cf0a0bb8 100644 --- a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift @@ -62,7 +62,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode { } else { peersNode = ChatListSearchRecentPeersNode(context: context, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in self?.controllerInteraction?.togglePeer(EngineRenderedPeer(peer: peer), true) - }, peerContextAction: { _, _, gesture in gesture?.cancel() }, isPeerSelected: { [weak self] peerId in + }, peerContextAction: { _, _, gesture, _ in gesture?.cancel() }, isPeerSelected: { [weak self] peerId in return self?.controllerInteraction?.selectedPeerIds.contains(peerId) ?? false }, share: true) self.peersNode = peersNode diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ee1194d0b2..a7dfbfd8c7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -952,7 +952,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage) }, openPeerMention: { [weak self] name in self?.openPeerMention(name) - }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer in + }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in guard let strongSelf = self, strongSelf.isNodeLoaded else { return } @@ -1225,7 +1225,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: selectAll)), items: actionsSignal, recognizer: recognizer, gesture: gesture) + let source: ContextContentSource + if let location = location { + source = .location(ChatMessageContextLocationContentSource(controller: strongSelf, location: node.view.convert(node.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) + } else { + source = .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: selectAll)) + } + + let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: actionsSignal, recognizer: recognizer, gesture: gesture) controller.immediateItemsTransitionAnimation = disableTransitionAnimations controller.getOverlayViews = { [weak self] in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 75332b03d1..8c383a695e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -61,7 +61,7 @@ public final class ChatControllerInteraction { let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void let openPeerMention: (String) -> Void - let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void + let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, String) -> Void let activateMessagePinch: (PinchSourceContainerNode) -> Void @@ -166,7 +166,7 @@ public final class ChatControllerInteraction { openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Peer?) -> Void, openPeerMention: @escaping (String) -> Void, - openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, + openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, String) -> Void, updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void, activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void, diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 90c1257da4..63d9a64d76 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -197,7 +197,7 @@ extension ListMessageItemInteraction { self.init(openMessage: { message, mode -> Bool in return controllerInteraction.openMessage(message, mode) }, openMessageContextMenu: { message, bool, node, rect, gesture in - controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture) + controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil) }, toggleMessagesSelection: { messageId, selected in controllerInteraction.toggleMessagesSelection(messageId, selected) }, openUrl: { url, param1, param2, message in diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 7673f8f510..58a6c6db69 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -267,7 +267,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { case .action, .optionalAction: break case let .openContextMenu(tapMessage, selectAll, subFrame): - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil) } } } @@ -750,7 +750,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } case .options: if let item = self.item { - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } } } @@ -1512,7 +1512,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if canAddMessageReactions(message: item.message) { item.controllerInteraction.updateMessageReaction(item.message, .default) } else { - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil, nil) } } } else if case .tap = gesture { @@ -1999,7 +1999,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } return nil - case .longTap, .doubleTap: + case .longTap, .doubleTap, .secondaryTap: if let item = self.item, self.imageNode.frame.contains(location) { return .openContextMenu(tapMessage: item.message, selectAll: false, subFrame: self.imageNode.frame) } @@ -2441,7 +2441,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { guard let item = self.item else { return } - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } override func targetReactionView(value: String) -> UIView? { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index c632945b36..817ea279ce 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -602,7 +602,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode case .action, .optionalAction: break case let .openContextMenu(tapMessage, selectAll, subFrame): - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil) } } } @@ -945,6 +945,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } } + recognizer.secondaryTap = { [weak self] point, recognizer in + guard let strongSelf = self, let item = strongSelf.item else { + return + } + + if let action = strongSelf.gestureRecognized(gesture: .secondaryTap, location: point, recognizer: recognizer) { + switch action { + case .action, .optionalAction: + break + case let .openContextMenu(tapMessage, selectAll, subFrame): + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, nil, point) + } + } + } recognizer.highlight = { [weak self] point in if let strongSelf = self { for contentNode in strongSelf.contentNodes { @@ -3051,7 +3065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } } - item.controllerInteraction.openMessageContextMenu(item.message, false, self, subFrame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, subFrame, nil, nil) } } } @@ -3128,7 +3142,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if canAddMessageReactions(message: tapMessage) { item.controllerInteraction.updateMessageReaction(tapMessage, .default) } else { - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil, nil) } } } else if case .tap = gesture { @@ -3365,7 +3379,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } return nil - case .longTap, .doubleTap: + case .longTap, .doubleTap, .secondaryTap: if let item = self.item, self.backgroundNode.frame.contains(location) { let message = item.message @@ -3957,7 +3971,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return } let subFrame = self.backgroundNode.frame - item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil) } override func targetReactionView(value: String) -> UIView? { diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index 61feec2d12..319ca3f95e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -5,6 +5,21 @@ import ContextUI import Postbox import TelegramCore import SwiftSignalKit + +final class ChatMessageContextLocationContentSource: ContextLocationContentSource { + private let controller: ViewController + private let location: CGPoint + + init(controller: ViewController, location: CGPoint) { + self.controller = controller + self.location = location + } + + func transitionInfo() -> ContextControllerLocationViewInfo? { + return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = false diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index ea5cbd4cf7..6766f2010a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -127,7 +127,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD break case let .openContextMenu(tapMessage, selectAll, subFrame): strongSelf.recognizer?.cancel() - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil) if (strongSelf.appliedCurrentlyPlaying ?? false) && strongSelf.interactiveVideoNode.isPlaying { strongSelf.wasPlaying = true strongSelf.interactiveVideoNode.pause() @@ -241,7 +241,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } case .options: if let item = self.item { - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.interactiveVideoNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.interactiveVideoNode.frame, nil, nil) } } } @@ -932,7 +932,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } return nil - case .longTap, .doubleTap: + case .longTap, .doubleTap, .secondaryTap: if let item = self.item, self.interactiveVideoNode.frame.contains(location) { return .openContextMenu(tapMessage: item.message, selectAll: false, subFrame: self.interactiveVideoNode.frame) } @@ -1308,7 +1308,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD guard let item = self.item else { return } - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.interactiveVideoNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.interactiveVideoNode.frame, nil, nil) } private var absoluteRect: (CGRect, CGSize)? diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 2e50baecd8..799220b1cd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -787,7 +787,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } self.item?.controllerInteraction.clickThroughMessage() - case .longTap, .doubleTap: + case .longTap, .doubleTap, .secondaryTap: break case .hold: break diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 7724544ea2..abc5f2b60d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -141,7 +141,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { case .action, .optionalAction: break case let .openContextMenu(tapMessage, selectAll, subFrame): - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture, nil) } } } @@ -322,7 +322,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } case .options: if let item = self.item { - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } } } @@ -1105,7 +1105,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if canAddMessageReactions(message: item.message) { item.controllerInteraction.updateMessageReaction(tapMessage, .default) } else { - item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil) + item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil, nil) } } } else if case .tap = gesture { @@ -1165,7 +1165,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } return nil - case .longTap, .doubleTap: + case .longTap, .doubleTap, .secondaryTap: if let item = self.item, self.imageNode.frame.contains(location) { return .openContextMenu(tapMessage: item.message, selectAll: false, subFrame: self.imageNode.frame) } @@ -1610,7 +1610,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { guard let item = self.item else { return } - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil) + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } override func targetReactionView(value: String) -> UIView? { diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 5fa9774f2f..26672d5613 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -254,8 +254,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } }, openPeerMention: { [weak self] name in self?.openPeerMention(name) - }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in - self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame) + }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _, location in + self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, location: location) }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in @@ -795,7 +795,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { })) } - private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect) { + private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, location: CGPoint?) { var actions: [ContextMenuAction] = [] if !message.text.isEmpty { actions.append(ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index e9a78f14e5..159cea8ccd 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -225,7 +225,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { }, hidePsa: { _ in - }, activateChatPreview: { [weak self] item, node, gesture in + }, activateChatPreview: { [weak self] item, node, gesture, _ in guard let strongSelf = self else { gesture?.cancel() return diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 1c7310f7a9..b0c1c61aa2 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -59,9 +59,9 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.displayDeviceContacts = displayDeviceContacts self.displayCallIcons = displayCallIcons - var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? - self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture in - contextActionImpl?(peer, node, gesture) + var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? + self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _ in + contextActionImpl?(peer, node, gesture, nil) } : nil, multipleSelection: multipleSelection) self.dimNode = ASDisplayNode() @@ -105,7 +105,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { self?.requestMultipleAction?(false, nil) } - contextActionImpl = { [weak self] peer, node, gesture in + contextActionImpl = { [weak self] peer, node, gesture, _ in if let strongSelf = self, (strongSelf.selectionState?.selectedPeerIndices.isEmpty ?? true) { strongSelf.contactListNode.updateSelectionState { state in let peerId = ContactListPeerId.peer(peer.id) diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 97ea2d7c5e..ecfa7ff302 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -109,7 +109,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { var selectStickerImpl: ((FileMediaReference, UIView, CGRect) -> Bool)? self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 928371d929..96d9135a80 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -69,7 +69,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in - }, openMessageContextMenu: { _, _, _, _, _ in + }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index c8ca92ec23..2d175e3795 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -1690,7 +1690,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro return chatControllerInteraction.openMessage(message, mode) }, openMessageContextMenu: { message, bool, node, rect, gesture in - chatControllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture) + chatControllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil) }, toggleMessagesSelection: { messageId, selected in chatControllerInteraction.toggleMessagesSelection(messageId, selected) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a6674e796c..9a18862f15 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1878,7 +1878,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self?.openPeer(peerId: id, navigation: navigation) } }, openPeerMention: { _ in - }, openMessageContextMenu: { [weak self] message, _, node, frame, anyRecognizer in + }, openMessageContextMenu: { [weak self] message, _, node, frame, anyRecognizer, _ in guard let strongSelf = self, let node = node as? ContextExtractedContentContainingNode else { return } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 00ae018e33..d9342e5139 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1274,7 +1274,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { let controllerInteraction: ChatControllerInteraction controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: { message in