iPad trackpad support improvements

This commit is contained in:
Ilya Laktyushin 2022-07-27 21:49:25 +03:00
parent 689a5e0427
commit f1ccd3cf33
47 changed files with 526 additions and 197 deletions

View File

@ -1885,6 +1885,8 @@ plist_fragment(
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
""".format(
telegram_bundle_id = telegram_bundle_id,
)

View File

@ -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))

View File

@ -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)
}
}

View File

@ -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)?

View File

@ -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
})

View File

@ -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<EngineMessage.Id>?
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<EngineMessage.Id>?) {
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<EngineMessage.Id>?) {
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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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

View File

@ -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 })

View File

@ -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

View File

@ -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()
}

View File

@ -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<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, 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<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, 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

View File

@ -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()

View File

@ -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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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

View File

@ -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)

View File

@ -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<Bool>()
let contentReady = Promise<Bool>()
@ -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<Bool, NoError> { get }
func transitionInfo() -> ContextControllerLocationViewInfo?
}
public extension ContextLocationContentSource {
var shouldBeDismissed: Signal<Bool, NoError> {
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

View File

@ -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)

View File

@ -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)

View File

@ -4623,38 +4623,61 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
override open func touchesEnded(_ touches: Set<UITouch>, 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()

View File

@ -96,4 +96,7 @@ public extension ListViewItem {
func selected(listView: ListView) {
}
func performSecondaryAction(listView: ListView) {
}
}

View File

@ -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
}

View File

@ -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
}
}
}
}

View File

@ -72,7 +72,7 @@ public final class HashtagSearchController: TelegramBaseController {
}, togglePeerMarkedUnread: { _, _ in
}, toggleArchivedFolderHiddenByDefault: {
}, hidePsa: { _ in
}, activateChatPreview: { _, _, gesture in
}, activateChatPreview: { _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in
})

View File

@ -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 {

View File

@ -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 {

View File

@ -826,7 +826,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
item.interaction.openUrl(url, false, true, nil)
}
}
case .hold, .doubleTap:
case .hold, .doubleTap, .secondaryTap:
break
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 })

View File

@ -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
})

View File

@ -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
})

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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? {

View File

@ -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? {

View File

@ -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

View File

@ -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)?

View File

@ -787,7 +787,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
self.item?.controllerInteraction.clickThroughMessage()
case .longTap, .doubleTap:
case .longTap, .doubleTap, .secondaryTap:
break
case .hold:
break

View File

@ -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? {

View File

@ -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: {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -69,7 +69,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}
}, openPeer: { _, _, _, _ in
}, openPeerMention: { _ in
}, openMessageContextMenu: { _, _, _, _, _ in
}, openMessageContextMenu: { _, _, _, _, _, _ in
}, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in
}, activateMessagePinch: { _ in

View File

@ -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)

View File

@ -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
}

View File

@ -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