mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Topics search
This commit is contained in:
parent
20b9eea20f
commit
ad2b8811c1
@ -8132,3 +8132,7 @@ Sorry for the inconvenience.";
|
||||
"ChatList.CloseAction" = "Close";
|
||||
|
||||
"Channel.EditAdmin.PermissionCreateTopics" = "Create Topics";
|
||||
|
||||
"ChatList.Search.FilterTopics" = "Topics";
|
||||
|
||||
"DialogList.SearchSectionTopics" = "Topics";
|
||||
|
@ -614,6 +614,7 @@ public final class ContactSelectionControllerParams {
|
||||
|
||||
public enum ChatListSearchFilter: Equatable {
|
||||
case chats
|
||||
case topics
|
||||
case media
|
||||
case downloads
|
||||
case links
|
||||
@ -627,18 +628,20 @@ public enum ChatListSearchFilter: Equatable {
|
||||
switch self {
|
||||
case .chats:
|
||||
return 0
|
||||
case .media:
|
||||
case .topics:
|
||||
return 1
|
||||
case .downloads:
|
||||
case .media:
|
||||
return 2
|
||||
case .links:
|
||||
return 3
|
||||
case .files:
|
||||
case .downloads:
|
||||
return 4
|
||||
case .music:
|
||||
case .links:
|
||||
return 5
|
||||
case .voice:
|
||||
case .files:
|
||||
return 6
|
||||
case .music:
|
||||
return 7
|
||||
case .voice:
|
||||
return 8
|
||||
case let .peer(peerId, _, _, _):
|
||||
return peerId.id._internalGetInt64Value()
|
||||
case let .date(_, date, _):
|
||||
|
@ -29,6 +29,7 @@ public enum ChatListSearchItemHeaderType {
|
||||
case subscribers
|
||||
case downloading
|
||||
case recentDownloads
|
||||
case topics
|
||||
|
||||
fileprivate func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
@ -80,6 +81,8 @@ public enum ChatListSearchItemHeaderType {
|
||||
return strings.DownloadList_DownloadingHeader
|
||||
case .recentDownloads:
|
||||
return strings.DownloadList_DownloadedHeader
|
||||
case .topics:
|
||||
return strings.DialogList_SearchSectionTopics
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +136,8 @@ public enum ChatListSearchItemHeaderType {
|
||||
return .downloading
|
||||
case .recentDownloads:
|
||||
return .recentDownloads
|
||||
case .topics:
|
||||
return .topics
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,6 +171,7 @@ private enum ChatListSearchItemHeaderId: Int32 {
|
||||
case subscribers
|
||||
case downloading
|
||||
case recentDownloads
|
||||
case topics
|
||||
}
|
||||
|
||||
public final class ChatListSearchItemHeader: ListViewItemHeader {
|
||||
|
@ -840,7 +840,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
if !previewing {
|
||||
let placeholder: String
|
||||
let compactPlaceholder: String
|
||||
|
||||
var isForum = false
|
||||
if case .forum = location {
|
||||
isForum = true
|
||||
placeholder = self.presentationData.strings.Common_Search
|
||||
compactPlaceholder = self.presentationData.strings.Common_Search
|
||||
} else {
|
||||
@ -849,7 +852,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder, activate: { [weak self] in
|
||||
self?.activateSearch()
|
||||
self?.activateSearch(filter: isForum ? .topics : .chats)
|
||||
})
|
||||
self.searchContentNode?.updateExpansionProgress(0.0)
|
||||
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||
@ -1429,7 +1432,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, messageId, deactivateOnAction in
|
||||
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, threadId, messageId, deactivateOnAction in
|
||||
if let strongSelf = self {
|
||||
strongSelf.openMessageFromSearchDisposable.set((strongSelf.context.engine.peers.ensurePeerIsLocallyAvailable(peer: peer)
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] actualPeerId in
|
||||
|
@ -1127,7 +1127,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((EnginePeer, Bool) -> Void)?
|
||||
var requestOpenRecentPeerOptions: ((EnginePeer) -> Void)?
|
||||
var requestOpenMessageFromSearch: ((EnginePeer, EngineMessage.Id, Bool) -> Void)?
|
||||
var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void)?
|
||||
var requestAddContact: ((String) -> Void)?
|
||||
var peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
|
||||
var dismissSelfIfCompletedPresentation: (() -> Void)?
|
||||
@ -1298,20 +1298,17 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
return nil
|
||||
}
|
||||
guard case let .chatList(groupId) = self.location else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let filter: ChatListNodePeersFilter = []
|
||||
|
||||
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, groupId: groupId, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in
|
||||
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: location, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in
|
||||
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
|
||||
}, openDisabledPeer: { _ in
|
||||
}, openRecentPeerOptions: { [weak self] peer in
|
||||
self?.requestOpenRecentPeerOptions?(peer)
|
||||
}, openMessage: { [weak self] peer, messageId, deactivateOnAction in
|
||||
}, openMessage: { [weak self] peer, threadId, messageId, deactivateOnAction in
|
||||
if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch {
|
||||
requestOpenMessageFromSearch(peer, messageId, deactivateOnAction)
|
||||
requestOpenMessageFromSearch(peer, threadId, messageId, deactivateOnAction)
|
||||
}
|
||||
}, addContact: { [weak self] phoneNumber in
|
||||
if let requestAddContact = self?.requestAddContact {
|
||||
|
@ -36,6 +36,7 @@ import MultiAnimationRenderer
|
||||
|
||||
private enum ChatListTokenId: Int32 {
|
||||
case archive
|
||||
case forum
|
||||
case filter
|
||||
case peer
|
||||
case date
|
||||
@ -44,7 +45,7 @@ private enum ChatListTokenId: Int32 {
|
||||
final class ChatListSearchInteraction {
|
||||
let openPeer: (EnginePeer, EnginePeer?, Bool) -> Void
|
||||
let openDisabledPeer: (EnginePeer) -> Void
|
||||
let openMessage: (EnginePeer, EngineMessage.Id, Bool) -> Void
|
||||
let openMessage: (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void
|
||||
let openUrl: (String) -> Void
|
||||
let clearRecentSearch: () -> Void
|
||||
let addContact: (String) -> Void
|
||||
@ -56,7 +57,7 @@ final class ChatListSearchInteraction {
|
||||
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?, CGPoint?) -> 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, Int64?, 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
|
||||
@ -84,11 +85,11 @@ private struct ChatListSearchContainerNodeSearchState: Equatable {
|
||||
public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private let context: AccountContext
|
||||
private let peersFilter: ChatListNodePeersFilter
|
||||
private let groupId: EngineChatList.Group
|
||||
private let location: ChatListControllerLocation
|
||||
private let displaySearchFilters: Bool
|
||||
private let hasDownloads: Bool
|
||||
private var interaction: ChatListSearchInteraction?
|
||||
private let openMessage: (EnginePeer, EngineMessage.Id, Bool) -> Void
|
||||
private let openMessage: (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void
|
||||
private let navigationController: NavigationController?
|
||||
|
||||
let filterContainerNode: ChatListSearchFiltersContainerNode
|
||||
@ -111,6 +112,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private let suggestedDates = Promise<[(Date?, Date, String?)]>([])
|
||||
private var suggestedFilters: [ChatListSearchFilter]?
|
||||
private let suggestedFiltersDisposable = MetaDisposable()
|
||||
private var forumPeer: EnginePeer?
|
||||
|
||||
private var shareStatusDisposable: MetaDisposable?
|
||||
|
||||
@ -131,10 +133,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, 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?) {
|
||||
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, 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, Int64?, 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
|
||||
self.location = location
|
||||
self.displaySearchFilters = displaySearchFilters
|
||||
self.hasDownloads = hasDownloads
|
||||
self.navigationController = navigationController
|
||||
@ -148,7 +150,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
|
||||
self.filterContainerNode = ChatListSearchFiltersContainerNode()
|
||||
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, groupId: groupId, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
||||
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
||||
self.paneContainerNode.clipsToBounds = true
|
||||
|
||||
super.init()
|
||||
@ -164,8 +166,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}, openDisabledPeer: { peer in
|
||||
openDisabledPeer(peer)
|
||||
}, openMessage: { peer, messageId, deactivateOnAction in
|
||||
originalOpenMessage(peer, messageId, deactivateOnAction)
|
||||
}, openMessage: { peer, threadId, messageId, deactivateOnAction in
|
||||
originalOpenMessage(peer, threadId, messageId, deactivateOnAction)
|
||||
if peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))]))
|
||||
}
|
||||
@ -248,6 +250,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
switch key {
|
||||
case .chats:
|
||||
filterKey = .chats
|
||||
case .topics:
|
||||
filterKey = .topics
|
||||
case .media:
|
||||
filterKey = .media
|
||||
case .downloads:
|
||||
@ -270,7 +274,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
if let suggestedFilters = strongSelf.suggestedFilters, !suggestedFilters.isEmpty {
|
||||
filters = suggestedFilters
|
||||
} else {
|
||||
filters = defaultAvailableSearchPanes(hasDownloads: strongSelf.hasDownloads).map(\.filter)
|
||||
var isForum = false
|
||||
if case .forum = strongSelf.location {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: strongSelf.hasDownloads).map(\.filter)
|
||||
}
|
||||
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
}
|
||||
@ -289,6 +298,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
switch filter {
|
||||
case .chats:
|
||||
key = .chats
|
||||
case .topics:
|
||||
key = .topics
|
||||
case .media:
|
||||
key = .media
|
||||
case .downloads:
|
||||
@ -318,38 +329,43 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
let searchQuerySignal = self.searchQuery.get()
|
||||
|
||||
let suggestedPeers = self.selectedFilterPromise.get()
|
||||
|> map { filter -> Bool in
|
||||
guard let filter = filter else {
|
||||
return false
|
||||
}
|
||||
switch filter {
|
||||
case let .filter(filter):
|
||||
switch filter {
|
||||
case .downloads:
|
||||
let suggestedPeers: Signal<[EnginePeer], NoError>
|
||||
if case .chatList = location {
|
||||
suggestedPeers = self.selectedFilterPromise.get()
|
||||
|> map { filter -> Bool in
|
||||
guard let filter = filter else {
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
switch filter {
|
||||
case let .filter(filter):
|
||||
switch filter {
|
||||
case .downloads:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { value -> Signal<String?, NoError> in
|
||||
if value {
|
||||
return searchQuerySignal
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { query -> Signal<[EnginePeer], NoError> in
|
||||
if let query = query {
|
||||
return context.account.postbox.searchPeers(query: query.lowercased())
|
||||
|> map { local -> [EnginePeer] in
|
||||
return Array(local.compactMap { $0.peer }.prefix(10).map(EnginePeer.init))
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { value -> Signal<String?, NoError> in
|
||||
if value {
|
||||
return searchQuerySignal
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
|> mapToSignal { query -> Signal<[EnginePeer], NoError> in
|
||||
if let query = query {
|
||||
return context.account.postbox.searchPeers(query: query.lowercased())
|
||||
|> map { local -> [EnginePeer] in
|
||||
return Array(local.compactMap { $0.peer }.prefix(10).map(EnginePeer.init))
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
suggestedPeers = .single([])
|
||||
}
|
||||
|
||||
let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
||||
@ -446,6 +462,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
})
|
||||
|
||||
if case let .forum(peerId) = location {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
self?.forumPeer = peer
|
||||
self?.updateSearchOptions(nil)
|
||||
})
|
||||
}
|
||||
|
||||
self._ready.set(self.paneContainerNode.isReady.get()
|
||||
|> map { _ in Void() })
|
||||
}
|
||||
@ -493,8 +517,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) {
|
||||
var options = options
|
||||
var tokens: [SearchBarToken] = []
|
||||
if self.groupId == .archive {
|
||||
if case .chatList(.archive) = self.location {
|
||||
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true))
|
||||
} else if case .forum = self.location, let forumPeer = self.forumPeer {
|
||||
tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true))
|
||||
}
|
||||
|
||||
if options?.isEmpty ?? true {
|
||||
@ -547,6 +573,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
public func search(filter: ChatListSearchFilter, query: String?) {
|
||||
let key: ChatListSearchPaneKey
|
||||
switch filter {
|
||||
case .topics:
|
||||
key = .topics
|
||||
case .media:
|
||||
key = .media
|
||||
case .links:
|
||||
@ -567,8 +595,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.searchTextUpdated(text: query ?? "")
|
||||
|
||||
var tokens: [SearchBarToken] = []
|
||||
if self.groupId == .archive {
|
||||
if case .chatList(.archive) = self.location {
|
||||
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true))
|
||||
} else if case .forum = self.location, let forumPeer = self.forumPeer {
|
||||
tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true))
|
||||
}
|
||||
self.setQuery?(nil, tokens, query ?? "")
|
||||
}
|
||||
@ -581,18 +611,23 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
let topInset = navigationBarHeight
|
||||
transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0)))
|
||||
|
||||
var isForum = false
|
||||
if case .forum = self.location {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
let filters: [ChatListSearchFilter]
|
||||
if let suggestedFilters = self.suggestedFilters, !suggestedFilters.isEmpty {
|
||||
filters = suggestedFilters
|
||||
} else {
|
||||
filters = defaultAvailableSearchPanes(hasDownloads: self.hasDownloads).map(\.filter)
|
||||
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads).map(\.filter)
|
||||
}
|
||||
|
||||
let overflowInset: CGFloat = 20.0
|
||||
self.filterContainerNode.update(size: CGSize(width: layout.size.width - overflowInset * 2.0, height: 38.0), sideInset: layout.safeInsets.left - overflowInset, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
var bottomIntrinsicInset = layout.intrinsicInsets.bottom
|
||||
if case .root = self.groupId {
|
||||
if case .chatList(.root) = self.location {
|
||||
if layout.safeInsets.left > overflowInset {
|
||||
bottomIntrinsicInset -= 34.0
|
||||
} else {
|
||||
@ -752,15 +787,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
bottomInset = inputHeight
|
||||
} else if let _ = self.selectionPanelNode {
|
||||
bottomInset = bottomIntrinsicInset
|
||||
} else if case .root = self.groupId {
|
||||
} else if case .chatList(.root) = self.location {
|
||||
bottomInset -= bottomIntrinsicInset
|
||||
}
|
||||
|
||||
let availablePanes: [ChatListSearchPaneKey]
|
||||
if self.displaySearchFilters {
|
||||
availablePanes = defaultAvailableSearchPanes(hasDownloads: self.hasDownloads)
|
||||
availablePanes = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads)
|
||||
} else {
|
||||
availablePanes = [.chats]
|
||||
availablePanes = isForum ? [.topics] : [.chats]
|
||||
}
|
||||
|
||||
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: bottomInset, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition)
|
||||
@ -885,7 +920,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: { [weak self] in
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false)
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
|
||||
})
|
||||
})))
|
||||
|
||||
@ -964,7 +999,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: { [weak self] in
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false)
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
|
||||
})
|
||||
})))
|
||||
|
||||
@ -1010,7 +1045,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false)
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
|
||||
})
|
||||
})))
|
||||
|
||||
@ -1062,7 +1097,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
public override func searchTextClearTokens() {
|
||||
self.updateSearchOptions(nil)
|
||||
self.setQuery?(nil, [], self.searchQueryValue ?? "")
|
||||
// self.setQuery?(nil, [], self.searchQueryValue ?? "")
|
||||
}
|
||||
|
||||
func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
|
||||
|
@ -81,6 +81,9 @@ private final class ItemNode: ASDisplayNode {
|
||||
case .chats:
|
||||
title = presentationData.strings.ChatList_Search_FilterChats
|
||||
icon = nil
|
||||
case .topics:
|
||||
title = presentationData.strings.ChatList_Search_FilterTopics
|
||||
icon = nil
|
||||
case .media:
|
||||
title = presentationData.strings.ChatList_Search_FilterMedia
|
||||
icon = nil
|
||||
|
@ -239,6 +239,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
public enum ChatListSearchEntryStableId: Hashable {
|
||||
case threadId(Int64)
|
||||
case localPeerId(EnginePeer.Id)
|
||||
case globalPeerId(EnginePeer.Id)
|
||||
case messageId(EngineMessage.Id, ChatListSearchEntry.MessageSection)
|
||||
@ -297,21 +298,24 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
case recentlyDownloaded
|
||||
}
|
||||
|
||||
case topic(EnginePeer, ChatListItemContent.ThreadInfo, Int, PresentationTheme, PresentationStrings, ChatListSearchSectionExpandType)
|
||||
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
|
||||
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
|
||||
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
|
||||
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool)
|
||||
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool)
|
||||
case addContact(String, PresentationTheme, PresentationStrings)
|
||||
|
||||
public var stableId: ChatListSearchEntryStableId {
|
||||
switch self {
|
||||
case let .topic(_, threadInfo, _, _, _, _):
|
||||
return .threadId(threadInfo.id)
|
||||
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _):
|
||||
return .localPeerId(peer.id)
|
||||
case let .localPeer(peer, _, _, _, _, _, _, _, _):
|
||||
return .localPeerId(peer.id)
|
||||
case let .globalPeer(peer, _, _, _, _, _, _, _):
|
||||
return .globalPeerId(peer.peer.id)
|
||||
case let .message(message, _, _, _, _, _, _, _, _, section, _):
|
||||
case let .message(message, _, _, _, _, _, _, _, _, _, section, _):
|
||||
return .messageId(message.id, section)
|
||||
case .addContact:
|
||||
return .addContact
|
||||
@ -320,6 +324,12 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
|
||||
public static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .topic(lhsPeer, lhsThreadInfo, lhsIndex, lhsTheme, lhsStrings, lhsExpandType):
|
||||
if case let .topic(rhsPeer, rhsThreadInfo, rhsIndex, rhsTheme, rhsStrings, rhsExpandType) = rhs, lhsPeer == rhsPeer, lhsThreadInfo == rhsThreadInfo, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsExpandType == rhsExpandType {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .recentlySearchedPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
|
||||
if case let .recentlySearchedPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
|
||||
return true
|
||||
@ -338,8 +348,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
|
||||
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
|
||||
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsThreadInfo, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
|
||||
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsThreadInfo, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
|
||||
if lhsMessage.id != rhsMessage.id {
|
||||
return false
|
||||
}
|
||||
@ -349,6 +359,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsThreadInfo != rhsThreadInfo {
|
||||
return false
|
||||
}
|
||||
if lhsPresentationData !== rhsPresentationData {
|
||||
return false
|
||||
}
|
||||
@ -403,15 +416,23 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
|
||||
public static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .topic(_, _, lhsIndex, _, _, _):
|
||||
if case let .topic(_, _, rhsIndex, _, _, _) = rhs {
|
||||
return lhsIndex <= rhsIndex
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
case let .recentlySearchedPeer(_, _, _, lhsIndex, _, _, _, _):
|
||||
if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
|
||||
if case .topic = rhs {
|
||||
return false
|
||||
} else if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
|
||||
return lhsIndex <= rhsIndex
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .recentlySearchedPeer:
|
||||
case .topic, .recentlySearchedPeer:
|
||||
return false
|
||||
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex <= rhsIndex
|
||||
@ -420,15 +441,15 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
}
|
||||
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .recentlySearchedPeer, .localPeer:
|
||||
case .topic, .recentlySearchedPeer, .localPeer:
|
||||
return false
|
||||
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex <= rhsIndex
|
||||
case .message, .addContact:
|
||||
return true
|
||||
}
|
||||
case let .message(_, _, _, _, _, _, _, lhsKey, _, _, _):
|
||||
if case let .message(_, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
|
||||
case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _):
|
||||
if case let .message(_, _, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
|
||||
return lhsKey < rhsKey
|
||||
} else if case .addContact = rhs {
|
||||
return true
|
||||
@ -440,8 +461,25 @@ 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?, 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 {
|
||||
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, 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 .topic(peer, threadInfo, _, theme, strings, expandType):
|
||||
let actionTitle: String?
|
||||
switch expandType {
|
||||
case .none:
|
||||
actionTitle = nil
|
||||
case .expand:
|
||||
actionTitle = strings.ChatList_Search_ShowMore
|
||||
case .collapse:
|
||||
actionTitle = strings.ChatList_Search_ShowLess
|
||||
}
|
||||
let header = ChatListSearchItemHeader(type: .topics, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : {
|
||||
toggleExpandGlobalResults()
|
||||
})
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .generalSearch, peer: .thread(peer: peer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: 0), status: .none, badge: nil, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
interaction.peerSelected(peer, threadInfo.id, nil, nil)
|
||||
}, contextAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
|
||||
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
|
||||
let primaryPeer: EnginePeer
|
||||
var chatPeer: EnginePeer?
|
||||
@ -664,7 +702,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location)
|
||||
}
|
||||
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
|
||||
case let .message(message, peer, readState, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused):
|
||||
case let .message(message, peer, readState, threadInfo, presentationData, _, selected, displayCustomHeader, orderingKey, _, _, allPaused):
|
||||
let header: ChatListSearchItemHeader
|
||||
switch orderingKey {
|
||||
case .downloading:
|
||||
@ -694,7 +732,21 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
if isMedia {
|
||||
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(id: peer.peerId), interaction: listInteraction, message: message._asMessage(), selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: key == .downloads ? header : nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: key != .downloads, isDownloadList: key == .downloads)
|
||||
} else {
|
||||
return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
let index: EngineChatList.Item.Index
|
||||
var chatThreadInfo: ChatListItemContent.ThreadInfo?
|
||||
switch location {
|
||||
case .chatList:
|
||||
index = .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index))
|
||||
case .forum:
|
||||
if let threadId = message.threadId, let threadInfo = threadInfo {
|
||||
chatThreadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadInfo)
|
||||
index = .forum(timestamp: message.index.timestamp, threadId: threadId, namespace: message.index.id.namespace, id: message.index.id.id)
|
||||
} else {
|
||||
index = .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index))
|
||||
}
|
||||
}
|
||||
|
||||
return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
}
|
||||
case let .addContact(phoneNumber, theme, strings):
|
||||
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
|
||||
@ -742,12 +794,12 @@ 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?, 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 {
|
||||
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, location: ChatListControllerLocation, 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) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||
|
||||
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated)
|
||||
}
|
||||
@ -851,7 +903,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
private var presentationData: PresentationData
|
||||
private let key: ChatListSearchPaneKey
|
||||
private let tagMask: EngineMessage.Tags?
|
||||
private let groupId: EngineChatList.Group?
|
||||
private let location: ChatListControllerLocation
|
||||
private let navigationController: NavigationController?
|
||||
|
||||
private let recentListNode: ListView
|
||||
@ -917,20 +969,29 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, groupId: EngineChatList.Group?, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.interaction = interaction
|
||||
self.key = key
|
||||
self.peersFilter = peersFilter
|
||||
self.groupId = groupId
|
||||
self.location = location
|
||||
self.navigationController = navigationController
|
||||
|
||||
var peersFilter = peersFilter
|
||||
if case .forum = location {
|
||||
peersFilter.insert(.excludeRecent)
|
||||
} else if case .chatList(.archive) = location {
|
||||
peersFilter.insert(.excludeRecent)
|
||||
}
|
||||
self.peersFilter = peersFilter
|
||||
|
||||
let tagMask: EngineMessage.Tags?
|
||||
switch key {
|
||||
case .chats:
|
||||
tagMask = nil
|
||||
case .topics:
|
||||
tagMask = nil
|
||||
case .media:
|
||||
tagMask = .photoOrVideo
|
||||
case .downloads:
|
||||
@ -1045,7 +1106,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let previousRecentlySearchedPeerOrder = Atomic<[EnginePeer.Id]>(value: [])
|
||||
let fixedRecentlySearchedPeers: Signal<[RecentlySearchedPeer], NoError>
|
||||
if case .chats = key {
|
||||
if case .chats = key, case .chatList(.root) = location {
|
||||
fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers()
|
||||
|> map { peers -> [RecentlySearchedPeer] in
|
||||
var result: [RecentlySearchedPeer] = []
|
||||
@ -1122,7 +1183,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let foundItems = combineLatest(queue: .mainQueue(), searchQuery, searchOptions, downloadItems)
|
||||
|> mapToSignal { [weak self] query, options, downloadItems -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
|
||||
if query == nil && options == nil && key == .chats {
|
||||
if query == nil && options == nil && [.chats, .topics].contains(key) {
|
||||
let _ = currentRemotePeers.swap(nil)
|
||||
return .single(nil)
|
||||
}
|
||||
@ -1186,7 +1247,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
resource = (resourceValue.id.stringRepresentation, size, entries.isEmpty)
|
||||
}
|
||||
|
||||
entries.append(.message(message, peer, nil, presentationData, 1, nil, false, .downloading(item.priority), resource, .downloading, allPaused))
|
||||
entries.append(.message(message, peer, nil, nil, presentationData, 1, nil, false, .downloading(item.priority), resource, .downloading, allPaused))
|
||||
}
|
||||
for item in downloadItems.doneItems.sorted(by: { ChatListSearchEntry.MessageOrderingKey.downloaded(timestamp: $0.timestamp, index: $0.message.index) < ChatListSearchEntry.MessageOrderingKey.downloaded(timestamp: $1.timestamp, index: $1.message.index) }) {
|
||||
if !item.isSeen {
|
||||
@ -1214,7 +1275,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(.message(message, peer, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
|
||||
entries.append(.message(message, peer, nil, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
|
||||
}
|
||||
return (entries.sorted(), false)
|
||||
}
|
||||
@ -1328,7 +1389,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError>
|
||||
let currentRemotePeersValue: ([FoundPeer], [FoundPeer]) = currentRemotePeers.with { $0 } ?? ([], [])
|
||||
if let query = query, tagMask == nil {
|
||||
if let query = query, case .chats = key {
|
||||
foundRemotePeers = (
|
||||
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true))
|
||||
|> then(
|
||||
@ -1340,22 +1401,26 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
} else {
|
||||
foundRemotePeers = .single(([], [], false))
|
||||
}
|
||||
let location: SearchMessagesLocation
|
||||
let searchLocation: SearchMessagesLocation
|
||||
if let options = options {
|
||||
if let (peerId, _, _) = options.peer {
|
||||
location = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
if case let .forum(peerId) = location {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
} else if let (peerId, _, _) = options.peer {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
} else {
|
||||
if let groupId = groupId {
|
||||
location = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
if case let .chatList(groupId) = location, case .archive = groupId {
|
||||
searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
} else {
|
||||
location = .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
searchLocation = .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let groupId = groupId {
|
||||
location = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)
|
||||
if case let .forum(peerId) = location {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil)
|
||||
} else if case let .chatList(groupId) = location, case .archive = groupId {
|
||||
searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)
|
||||
} else {
|
||||
location = .general(tags: tagMask, minDate: nil, maxDate: nil)
|
||||
searchLocation = .general(tags: tagMask, minDate: nil, maxDate: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1371,7 +1436,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
addAppLogEvent(postbox: context.account.postbox, type: "search_global_query")
|
||||
}
|
||||
|
||||
let searchSignal = context.engine.messages.searchMessages(location: location, query: finalQuery, state: nil, limit: 50)
|
||||
let searchSignal = context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50)
|
||||
|> map { result, updatedState -> ChatListSearchMessagesResult in
|
||||
return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues(EnginePeerReadCounters.init), hasMore: !result.completed, totalCount: result.totalCount, state: updatedState)
|
||||
}
|
||||
@ -1380,7 +1445,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|> mapToSignal { searchContext -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], Int32), Bool), NoError> in
|
||||
if let searchContext = searchContext, searchContext.result.hasMore {
|
||||
if let _ = searchContext.loadMoreIndex {
|
||||
return context.engine.messages.searchMessages(location: location, query: finalQuery, state: searchContext.result.state, limit: 80)
|
||||
return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: searchContext.result.state, limit: 80)
|
||||
|> map { result, updatedState -> ChatListSearchMessagesResult in
|
||||
return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues(EnginePeerReadCounters.init), hasMore: !result.completed, totalCount: result.totalCount, state: updatedState)
|
||||
}
|
||||
@ -1426,12 +1491,40 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
})
|
||||
|
||||
return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get(), searchStatePromise.get(), selectionPromise.get(), resolvedMessage, fixedRecentlySearchedPeers)
|
||||
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers -> ([ChatListSearchEntry], Bool)? in
|
||||
let foundThreads: Signal<([Int64: EngineMessageHistoryThread.Info], [EngineChatList.Item]), NoError> = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account)
|
||||
|> map { view -> ([Int64: EngineMessageHistoryThread.Info], [EngineChatList.Item]) in
|
||||
var itemsMap: [Int64: EngineMessageHistoryThread.Info] = [:]
|
||||
var filteredItems: [EngineChatList.Item] = []
|
||||
let queryTokens = stringIndexTokens(finalQuery, transliteration: .combined)
|
||||
for item in view.list.items {
|
||||
if case let .forum(_, index, _, _) = item.index, let threadInfo = item.threadInfo {
|
||||
itemsMap[index] = threadInfo
|
||||
}
|
||||
if !finalQuery.isEmpty {
|
||||
if let title = item.threadInfo?.title {
|
||||
let tokens = stringIndexTokens(title, transliteration: .combined)
|
||||
if matchStringIndexTokens(tokens, with: queryTokens) {
|
||||
filteredItems.append(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (itemsMap, filteredItems)
|
||||
}
|
||||
|
||||
return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get(), searchStatePromise.get(), selectionPromise.get(), resolvedMessage, fixedRecentlySearchedPeers, foundThreads)
|
||||
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads -> ([ChatListSearchEntry], Bool)? in
|
||||
let isSearching = foundRemotePeers.2 || foundRemoteMessages.1
|
||||
var entries: [ChatListSearchEntry] = []
|
||||
var index = 0
|
||||
|
||||
for thread in allAndFoundThreads.1 {
|
||||
if let peer = thread.renderedPeer.peer, let threadInfo = thread.threadInfo, case let .forum(_, id, _, _) = thread.index {
|
||||
entries.append(.topic(peer, ChatListItemContent.ThreadInfo(id: id, info: threadInfo), index, presentationData.theme, presentationData.strings, .none))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
var recentPeers = recentPeers
|
||||
if query != nil {
|
||||
recentPeers = []
|
||||
@ -1623,7 +1716,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
|
||||
}
|
||||
}
|
||||
entries.append(.message(message, peer, nil, presentationData, 1, nil, true, .index(message.index), nil, .generic, false))
|
||||
entries.append(.message(message, peer, nil, message.threadId.flatMap { allAndFoundThreads.0[$0] }, presentationData, 1, nil, true, .index(message.index), nil, .generic, false))
|
||||
index += 1
|
||||
}
|
||||
|
||||
@ -1646,12 +1739,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
|
||||
}
|
||||
}
|
||||
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2, selectionState?.contains(message.id), headerId == firstHeaderId, .index(message.index), nil, .generic, false))
|
||||
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], message.threadId.flatMap { allAndFoundThreads.0[$0] }, presentationData, foundRemoteMessages.0.2, selectionState?.contains(message.id), headerId == firstHeaderId, .index(message.index), nil, .generic, false))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
if tagMask == nil, !peersFilter.contains(.excludeRecent), isViablePhoneNumber(finalQuery) {
|
||||
if case .chats = key, !peersFilter.contains(.excludeRecent), isViablePhoneNumber(finalQuery) {
|
||||
entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings))
|
||||
}
|
||||
|
||||
@ -1727,10 +1820,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, togglePeerSelected: { _ in
|
||||
}, togglePeersSelection: { _, _ in
|
||||
}, additionalCategorySelected: { _ in
|
||||
}, messageSelected: { [weak self] peer, _, message, _ in
|
||||
}, messageSelected: { [weak self] peer, threadId, message, _ in
|
||||
interaction.dismissInput()
|
||||
if let strongSelf = self, let peer = message.peers[message.id.peerId] {
|
||||
interaction.openMessage(EnginePeer(peer), message.id, strongSelf.key == .chats)
|
||||
interaction.openMessage(EnginePeer(peer), threadId, message.id, strongSelf.key == .chats)
|
||||
}
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, groupSelected: { _ in
|
||||
@ -1826,7 +1919,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
var fetchResourceId: (id: String, size: Int64, isFirstInList: Bool)?
|
||||
for entry in currentEntries {
|
||||
switch entry {
|
||||
case let .message(m, _, _, _, _, _, _, _, resource, _, _):
|
||||
case let .message(m, _, _, _, _, _, _, _, _, resource, _, _):
|
||||
if m.id == message.id {
|
||||
fetchResourceId = resource
|
||||
}
|
||||
@ -1907,7 +2000,7 @@ 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, location in
|
||||
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, location: location, 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 {
|
||||
@ -1999,7 +2092,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
var messages: [EngineMessage] = []
|
||||
for entry in newEntries {
|
||||
if case let .message(message, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
messages.append(message)
|
||||
}
|
||||
}
|
||||
@ -2051,7 +2144,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
recentItems = .single([])
|
||||
}
|
||||
|
||||
if tagMask == nil && !peersFilter.contains(.excludeRecent) {
|
||||
if case .chats = key, !peersFilter.contains(.excludeRecent) {
|
||||
self.updatedRecentPeersDisposable.set(context.engine.peers.managedUpdatedRecentPeers().start())
|
||||
}
|
||||
|
||||
@ -2664,7 +2757,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if isFirstTime && self.key == .chats {
|
||||
if isFirstTime && [.chats, .topics].contains(self.key) {
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
} else if transition.animated {
|
||||
@ -2737,7 +2830,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
strongSelf.emptyResultsAnimationNode.visibility = emptyResults
|
||||
}
|
||||
|
||||
var displayPlaceholder = transition.isLoading && (strongSelf.key != .chats || (strongSelf.currentEntries?.isEmpty ?? true))
|
||||
var displayPlaceholder = transition.isLoading && (![.chats, .topics].contains(strongSelf.key) || (strongSelf.currentEntries?.isEmpty ?? true))
|
||||
if strongSelf.key == .downloads {
|
||||
displayPlaceholder = false
|
||||
}
|
||||
@ -2959,7 +3052,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
|
||||
let items = (0 ..< 2).compactMap { _ -> ListViewItem? in
|
||||
switch key {
|
||||
case .chats, .downloads:
|
||||
case .chats, .topics, .downloads:
|
||||
let message = EngineMessage(
|
||||
stableId: 0,
|
||||
stableVersion: 0,
|
||||
|
@ -702,7 +702,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var index: UInt32 = 0
|
||||
if let entries = entries {
|
||||
for entry in entries {
|
||||
if case let .message(message, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .message(message, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
self.mediaItems.append(VisualMediaItem(message: message._asMessage(), index: nil))
|
||||
}
|
||||
index += 1
|
||||
|
@ -49,6 +49,7 @@ final class ChatListSearchPaneWrapper {
|
||||
|
||||
public enum ChatListSearchPaneKey {
|
||||
case chats
|
||||
case topics
|
||||
case media
|
||||
case downloads
|
||||
case links
|
||||
@ -62,6 +63,8 @@ extension ChatListSearchPaneKey {
|
||||
switch self {
|
||||
case .chats:
|
||||
return .chats
|
||||
case .topics:
|
||||
return .topics
|
||||
case .media:
|
||||
return .media
|
||||
case .downloads:
|
||||
@ -78,9 +81,15 @@ extension ChatListSearchPaneKey {
|
||||
}
|
||||
}
|
||||
|
||||
func defaultAvailableSearchPanes(hasDownloads: Bool) -> [ChatListSearchPaneKey] {
|
||||
var result: [ChatListSearchPaneKey] = [.chats, .media, .downloads, .links, .files, .music, .voice]
|
||||
|
||||
func defaultAvailableSearchPanes(isForum: Bool, hasDownloads: Bool) -> [ChatListSearchPaneKey] {
|
||||
var result: [ChatListSearchPaneKey] = []
|
||||
if isForum {
|
||||
result.append(.topics)
|
||||
} else {
|
||||
result.append(.chats)
|
||||
}
|
||||
result.append(contentsOf: [.media, .downloads, .links, .files, .music, .voice])
|
||||
|
||||
if !hasDownloads {
|
||||
result.removeAll(where: { $0 == .downloads })
|
||||
}
|
||||
@ -110,13 +119,13 @@ private final class ChatListSearchPendingPane {
|
||||
interaction: ChatListSearchInteraction,
|
||||
navigationController: NavigationController?,
|
||||
peersFilter: ChatListNodePeersFilter,
|
||||
groupId: EngineChatList.Group,
|
||||
location: ChatListControllerLocation,
|
||||
searchQuery: Signal<String?, NoError>,
|
||||
searchOptions: Signal<ChatListSearchOptions?, NoError>,
|
||||
key: ChatListSearchPaneKey,
|
||||
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
||||
) {
|
||||
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], groupId: groupId, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
||||
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], location: location, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
||||
|
||||
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
||||
self.disposable = (paneNode.isReady
|
||||
@ -138,7 +147,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private let peersFilter: ChatListNodePeersFilter
|
||||
private let groupId: EngineChatList.Group
|
||||
private let location: ChatListControllerLocation
|
||||
private let searchQuery: Signal<String?, NoError>
|
||||
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
|
||||
private let navigationController: NavigationController?
|
||||
@ -172,13 +181,13 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
|
||||
private var currentAvailablePanes: [ChatListSearchPaneKey]?
|
||||
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peersFilter: ChatListNodePeersFilter, groupId: EngineChatList.Group, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peersFilter: ChatListNodePeersFilter, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.peersFilter = peersFilter
|
||||
self.groupId = groupId
|
||||
self.location = location
|
||||
self.searchQuery = searchQuery
|
||||
self.searchOptions = searchOptions
|
||||
self.navigationController = navigationController
|
||||
@ -408,7 +417,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
interaction: self.interaction!,
|
||||
navigationController: self.navigationController,
|
||||
peersFilter: self.peersFilter,
|
||||
groupId: self.groupId,
|
||||
location: self.location,
|
||||
searchQuery: self.searchQuery,
|
||||
searchOptions: self.searchOptions,
|
||||
key: key,
|
||||
|
@ -221,6 +221,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
case .deviceContact:
|
||||
break
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,11 +97,30 @@ public struct ContactsPeerItemAction {
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemPeer: Equatable {
|
||||
case thread(peer: EnginePeer, title: String, icon: Int64?, color: Int32)
|
||||
case peer(peer: EnginePeer?, chatPeer: EnginePeer?)
|
||||
case deviceContact(stableId: DeviceContactStableId, contact: DeviceContactBasicData)
|
||||
|
||||
public static func ==(lhs: ContactsPeerItemPeer, rhs: ContactsPeerItemPeer) -> Bool {
|
||||
switch lhs {
|
||||
case let .thread(lhsPeer, lhsTitle, lhsIcon, lhsColor):
|
||||
if case let .thread(rhsPeer, rhsTitle, rhsIcon, rhsColor) = rhs {
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsTitle != rhsTitle {
|
||||
return false
|
||||
}
|
||||
if lhsIcon != rhsIcon {
|
||||
return false
|
||||
}
|
||||
if lhsColor != rhsColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsPeer, lhsChatPeer):
|
||||
if case let .peer(rhsPeer, rhsChatPeer) = rhs {
|
||||
if lhsPeer != rhsPeer {
|
||||
@ -224,6 +243,8 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
if let index = index {
|
||||
var letter: String = "#"
|
||||
switch peer {
|
||||
case let .thread(_, title, _, _):
|
||||
letter = String(title.prefix(1)).uppercased()
|
||||
case let .peer(peer, _):
|
||||
if case let .user(user) = peer {
|
||||
switch index {
|
||||
@ -368,6 +389,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private let offsetContainerNode: ASDisplayNode
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private var avatarIconView: ComponentHostView<Empty>?
|
||||
private var avatarIconComponent: EmojiStatusComponent?
|
||||
private let titleNode: TextNode
|
||||
private var credibilityIconView: ComponentHostView<Empty>?
|
||||
private var credibilityIconComponent: EmojiStatusComponent?
|
||||
@ -389,6 +412,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
return chatPeer ?? peer
|
||||
case .deviceContact:
|
||||
return nil
|
||||
case .thread:
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
@ -426,6 +451,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
containerSize: credibilityIconView.bounds.size
|
||||
)
|
||||
}
|
||||
if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent {
|
||||
let _ = avatarIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent.withVisibleForAnimations(self.visibilityStatus)),
|
||||
environment: {},
|
||||
containerSize: avatarIconView.bounds.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -612,6 +645,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
var leftInset: CGFloat = 65.0 + params.leftInset
|
||||
var rightInset: CGFloat = 10.0 + params.rightInset
|
||||
|
||||
if case .thread = item.peer {
|
||||
leftInset -= 13.0
|
||||
}
|
||||
|
||||
let updatedSelectionNode: CheckNode?
|
||||
var isSelected = false
|
||||
switch item.selection {
|
||||
@ -657,6 +694,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
case .deviceContact:
|
||||
break
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
|
||||
var arrowButtonImage: UIImage?
|
||||
@ -698,6 +737,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
var userPresence: EnginePeer.Presence?
|
||||
|
||||
switch item.peer {
|
||||
case let .thread(_, title, _, _):
|
||||
titleAttributedString = NSAttributedString(string: title, font: titleBoldFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
case let .peer(peer, chatPeer):
|
||||
if let peer = peer {
|
||||
let textColor: UIColor
|
||||
@ -943,6 +984,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
letters = [" "]
|
||||
}
|
||||
strongSelf.avatarNode.setCustomLetters(letters)
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
@ -982,18 +1025,63 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
hasTopCorners = true
|
||||
strongSelf.topSeparatorNode.isHidden = hasCorners
|
||||
}
|
||||
}
|
||||
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
strongSelf.separatorNode.isHidden = false
|
||||
default:
|
||||
hasBottomCorners = true
|
||||
strongSelf.separatorNode.isHidden = hasCorners
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
strongSelf.separatorNode.isHidden = false
|
||||
default:
|
||||
hasBottomCorners = true
|
||||
strongSelf.separatorNode.isHidden = hasCorners
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)))
|
||||
|
||||
if case let .thread(_, title, icon, color) = item.peer {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
|
||||
let avatarIconView: ComponentHostView<Empty>
|
||||
if let current = strongSelf.avatarIconView {
|
||||
avatarIconView = current
|
||||
} else {
|
||||
avatarIconView = ComponentHostView<Empty>()
|
||||
strongSelf.avatarIconView = avatarIconView
|
||||
strongSelf.offsetContainerNode.view.addSubview(avatarIconView)
|
||||
}
|
||||
|
||||
let avatarIconContent: EmojiStatusComponent.Content
|
||||
if let fileId = icon, fileId != 0 {
|
||||
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
|
||||
} else {
|
||||
avatarIconContent = .topic(title: String(title.prefix(1)), colorIndex: Int(clamping: abs(color)), size: CGSize(width: 32.0, height: 32.0))
|
||||
}
|
||||
|
||||
let avatarIconComponent = EmojiStatusComponent(
|
||||
context: item.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
content: avatarIconContent,
|
||||
isVisibleForAnimations: strongSelf.visibilityStatus,
|
||||
action: nil
|
||||
)
|
||||
strongSelf.avatarIconComponent = avatarIconComponent
|
||||
|
||||
let iconSize = avatarIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(avatarIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
transition.updateFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 43.0, y: floor((nodeLayout.contentSize.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
} else if let avatarIconView = strongSelf.avatarIconView {
|
||||
strongSelf.avatarIconView = nil
|
||||
avatarIconView.removeFromSuperview()
|
||||
|
||||
strongSelf.avatarNode.isHidden = false
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let titleFrame = titleFrame.offsetBy(dx: revealOffset, dy: 0.0)
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||
@ -1269,6 +1357,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
case .deviceContact:
|
||||
break
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1282,6 +1372,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
case .deviceContact:
|
||||
break
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1296,6 +1388,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
case .deviceContact:
|
||||
break
|
||||
case .thread:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
item.options[Int(option.key)].action()
|
||||
|
@ -54,7 +54,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
|> map { result, presentationData in
|
||||
let result = result.0
|
||||
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap(EnginePeerReadCounters.init), chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false) })
|
||||
return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap(EnginePeerReadCounters.init), nil, chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false) })
|
||||
}
|
||||
let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {
|
||||
}, peerSelected: { _, _, _, _ in
|
||||
@ -106,7 +106,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
})
|
||||
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], location: .chatList(groupId: .root), key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
|
||||
}, toggleExpandGlobalResults: {
|
||||
}, searchPeer: { _ in
|
||||
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {})
|
||||
|
@ -14,9 +14,12 @@ swift_library(
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -3,9 +3,12 @@ import UIKit
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ActivityIndicator
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
|
||||
private func generateLoupeIcon(color: UIColor) -> UIImage? {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color)
|
||||
@ -42,14 +45,16 @@ public struct SearchBarToken {
|
||||
public let id: AnyHashable
|
||||
public let icon: UIImage?
|
||||
public let iconOffset: CGFloat?
|
||||
public let peer: (EnginePeer, AccountContext, PresentationTheme)?
|
||||
public let title: String
|
||||
public let style: Style?
|
||||
public let permanent: Bool
|
||||
|
||||
public init(id: AnyHashable, icon: UIImage?, iconOffset: CGFloat? = 0.0, title: String, style: Style? = nil, permanent: Bool) {
|
||||
public init(id: AnyHashable, icon: UIImage?, iconOffset: CGFloat? = 0.0, peer: (EnginePeer, AccountContext, PresentationTheme)? = nil, title: String, style: Style? = nil, permanent: Bool) {
|
||||
self.id = id
|
||||
self.icon = icon
|
||||
self.iconOffset = iconOffset
|
||||
self.peer = peer
|
||||
self.title = title
|
||||
self.style = style
|
||||
self.permanent = permanent
|
||||
@ -63,6 +68,7 @@ private final class TokenNode: ASDisplayNode {
|
||||
let iconNode: ASImageNode
|
||||
let titleNode: ASTextNode
|
||||
let backgroundNode: ASImageNode
|
||||
let avatarNode: AvatarNode?
|
||||
|
||||
var isSelected: Bool = false
|
||||
var isCollapsed: Bool = false
|
||||
@ -85,22 +91,34 @@ private final class TokenNode: ASDisplayNode {
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
|
||||
if let _ = token.peer {
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0))
|
||||
} else {
|
||||
self.avatarNode = nil
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
|
||||
let backgroundColor = token.style?.backgroundColor ?? theme.inputIcon
|
||||
let strokeColor = token.style?.strokeColor ?? backgroundColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
|
||||
let foregroundColor = token.style?.foregroundColor ?? .white
|
||||
self.iconNode.image = generateTintedImage(image: token.icon, color: foregroundColor)
|
||||
self.containerNode.addSubnode(self.iconNode)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
|
||||
self.containerNode.addSubnode(self.titleNode)
|
||||
if let avatarNode = self.avatarNode, let (peer, context, theme) = token.peer {
|
||||
avatarNode.setPeer(context: context, theme: theme, peer: peer, clipStyle: .roundedRect, displayDimensions: CGSize(width: 24.0, height: 24.0))
|
||||
self.containerNode.addSubnode(avatarNode)
|
||||
} else {
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
|
||||
let backgroundColor = token.style?.backgroundColor ?? theme.inputIcon
|
||||
let strokeColor = token.style?.strokeColor ?? backgroundColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
|
||||
let foregroundColor = token.style?.foregroundColor ?? .white
|
||||
self.iconNode.image = generateTintedImage(image: token.icon, color: foregroundColor)
|
||||
self.containerNode.addSubnode(self.iconNode)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
|
||||
self.containerNode.addSubnode(self.titleNode)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -110,7 +128,6 @@ private final class TokenNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func tapGesture() {
|
||||
|
||||
self.tapped?()
|
||||
}
|
||||
|
||||
@ -119,6 +136,11 @@ private final class TokenNode: ASDisplayNode {
|
||||
self.containerNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.backgroundNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
if let avatarNode = self.avatarNode {
|
||||
avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
self.titleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
@ -175,11 +197,17 @@ private final class TokenNode: ASDisplayNode {
|
||||
width += iconSize.width + 7.0
|
||||
}
|
||||
|
||||
let size = CGSize(width: self.isCollapsed ? height : width, height: height)
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize))
|
||||
|
||||
let size: CGSize
|
||||
if let avatarNode = self.avatarNode {
|
||||
size = CGSize(width: height, height: height)
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
} else {
|
||||
size = CGSize(width: self.isCollapsed ? height : width, height: height)
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize))
|
||||
}
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,9 @@ public final class SearchDisplayController {
|
||||
strongSelf.searchBar.tokens = tokens
|
||||
strongSelf.searchBar.text = query
|
||||
if previousTokens.count < tokens.count && !isFirstTime {
|
||||
strongSelf.searchBar.selectLastToken()
|
||||
if let lastToken = tokens.last, !lastToken.permanent {
|
||||
strongSelf.searchBar.selectLastToken()
|
||||
}
|
||||
}
|
||||
isFirstTime = false
|
||||
}
|
||||
|
@ -146,6 +146,11 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
let defaultColor: UIColor = peer.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor
|
||||
|
||||
var isForum = false
|
||||
if let peer = peer.chatMainPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
let text: String
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if peer.peerId == context.account.peerId {
|
||||
@ -162,7 +167,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
}
|
||||
self.textNode.maximumNumberOfLines = numberOfLines
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
|
||||
self.avatarNode.setPeer(context: context, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
self.avatarNode.setPeer(context: context, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoad)
|
||||
|
||||
let onlineLayout = self.onlineNode.asyncLayout()
|
||||
let (onlineSize, onlineApply) = onlineLayout(online, false)
|
||||
@ -182,16 +187,29 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
self.textNode.attributedText = NSAttributedString(string: attributedText.string, font: textFont, textColor: selected ? self.theme.selectedTextColor : (self.peer?.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center)
|
||||
}
|
||||
|
||||
var isForum = false
|
||||
if let peer = self.peer?.chatMainPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
if selected {
|
||||
self.avatarNode.transform = CATransform3DMakeScale(0.866666, 0.866666, 1.0)
|
||||
self.avatarSelectionNode.alpha = 1.0
|
||||
self.avatarSelectionNode.image = generateImage(CGSize(width: 60.0 + 4.0, height: 60.0 + 4.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(self.theme.selectedTextColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: size.width - 4.0, height: size.height - 4.0)))
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
if isForum {
|
||||
context.setStrokeColor(self.theme.selectedTextColor.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 1.0, dy: 1.0), cornerRadius: floorToScreenPixels(bounds.size.width * 0.26)).cgPath)
|
||||
context.strokePath()
|
||||
} else {
|
||||
context.setFillColor(self.theme.selectedTextColor.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: bounds.insetBy(dx: 2.0, dy: 2.0))
|
||||
}
|
||||
})
|
||||
if animated {
|
||||
self.avatarNode.layer.animateScale(from: 1.0, to: 0.866666, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
@ -64,7 +64,11 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
||||
if isVoice {
|
||||
refinedTag = .voiceOrInstantVideo
|
||||
} else {
|
||||
refinedTag = .music
|
||||
if file.isInstantVideo {
|
||||
refinedTag = .voiceOrInstantVideo
|
||||
} else {
|
||||
refinedTag = .music
|
||||
}
|
||||
}
|
||||
break inner
|
||||
case .Sticker:
|
||||
|
@ -64,7 +64,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
var requestOpenPeer: ((Peer) -> Void)?
|
||||
var requestOpenDisabledPeer: ((Peer) -> Void)?
|
||||
var requestOpenPeerFromSearch: ((Peer) -> Void)?
|
||||
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
|
||||
var requestOpenMessageFromSearch: ((Peer, Int64?, MessageId) -> Void)?
|
||||
var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
||||
|
||||
private var presentationData: PresentationData {
|
||||
@ -596,7 +596,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
animationRenderer: self.animationRenderer,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
filter: self.filter,
|
||||
groupId: EngineChatList.Group(.root),
|
||||
location: .chatList(groupId: EngineChatList.Group(.root)),
|
||||
displaySearchFilters: false,
|
||||
hasDownloads: false,
|
||||
openPeer: { [weak self] peer, chatPeer, _ in
|
||||
@ -650,9 +650,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
},
|
||||
openRecentPeerOptions: { _ in
|
||||
},
|
||||
openMessage: { [weak self] peer, messageId, _ in
|
||||
openMessage: { [weak self] peer, threadId, messageId, _ in
|
||||
if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch {
|
||||
requestOpenMessageFromSearch(peer._asPeer(), messageId)
|
||||
requestOpenMessageFromSearch(peer._asPeer(), threadId, messageId)
|
||||
}
|
||||
},
|
||||
addContact: nil,
|
||||
|
Loading…
x
Reference in New Issue
Block a user