Topics search

This commit is contained in:
Ilya Laktyushin 2022-10-15 17:44:04 +03:00
parent 20b9eea20f
commit ad2b8811c1
19 changed files with 467 additions and 163 deletions

View File

@ -8132,3 +8132,7 @@ Sorry for the inconvenience.";
"ChatList.CloseAction" = "Close";
"Channel.EditAdmin.PermissionCreateTopics" = "Create Topics";
"ChatList.Search.FilterTopics" = "Topics";
"DialogList.SearchSectionTopics" = "Topics";

View File

@ -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, _):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -221,6 +221,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
}
case .deviceContact:
break
case .thread:
break
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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