Search filters improvements

This commit is contained in:
Ilya Laktyushin 2020-09-09 22:16:58 +03:00
parent cbf67b1deb
commit 2864cb2987
25 changed files with 3950 additions and 3442 deletions

View File

@ -5748,10 +5748,11 @@ Any member of this group will be able to see messages in the channel.";
"ChatList.Search.FilterVideos" = "Video";
"ChatList.Search.FilterLinks" = "Links";
"ChatList.Search.FilterFiles" = "Files";
"ChatList.Search.FilterMusic" = "Audio";
"ChatList.Search.FilterMusic" = "Music";
"ChatList.Search.NoResults" = "No Results";
"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search.";
"ChatList.Search.NoResultsQueryDescription" = "There were no results for \"%@\".\nTry a new search.";
"ChatList.Search.NoResultsDescription" = "There were no results.\nTry a new search.";
"ChatList.Search.Messages_0" = "%@ messages";
"ChatList.Search.Messages_1" = "%@ message";

View File

@ -11,7 +11,7 @@ import UniversalMediaPlayer
public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
case peer(PeerId)
case recentActions(PeerId)
case searchResults
case custom
public func isEqual(to: SharedMediaPlaylistId) -> Bool {
if let to = to as? PeerMessagesMediaPlaylistId {
@ -25,7 +25,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId)
case singleMessage(MessageId)
case recentActions(Message)
case searchResults(query: String?, peerId: PeerId?, messages: [Message], at: MessageId)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
public var playlistId: PeerMessagesMediaPlaylistId {
switch self {
@ -35,14 +35,14 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
return .peer(id.peerId)
case let .recentActions(message):
return .recentActions(message.id.peerId)
case let .searchResults(query, peerId, messages, _):
return .searchResults
case .custom:
return .custom
}
}
public var messageId: MessageId? {
switch self {
case let .messages(_, _, messageId), let .singleMessage(messageId):
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _):
return messageId
default:
return nil
@ -77,8 +77,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
} else {
return false
}
case let .searchResults(lhsQuery, lhsPeerId, lhsMessages, lhsAt):
if case let .searchResults(rhsQuery, rhsPeerId, rhsMessages, rhsAt) = rhs, lhsQuery == rhsQuery, lhsPeerId == rhsPeerId, lhsAt == rhsAt {
case let .custom(_, lhsAt, _):
if case let .custom(_, rhsAt, _) = rhs, lhsAt == rhsAt {
return true
} else {
return false
@ -114,7 +114,7 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
if isGlobalSearch {
return (PeerMessagesMediaPlaylistId.searchResults, PeerMessagesMediaPlaylistItemId(messageId: message.id))
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id))
} else if isRecentActions {
return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id))
} else {

View File

@ -22,6 +22,7 @@ public enum ChatListSearchItemHeaderType {
case chatTypes
case faq
case messages(Int32)
case photos(Int32)
case links(Int32)
case files(Int32)
case music(Int32)
@ -62,6 +63,8 @@ public enum ChatListSearchItemHeaderType {
return strings.Settings_FrequentlyAskedQuestions
case let .messages(count):
return strings.ChatList_Search_Messages(count)
case let .photos(count):
return strings.ChatList_Search_Photos(count)
case let .links(count):
return strings.ChatList_Search_Links(count)
case let .files(count):
@ -107,6 +110,8 @@ public enum ChatListSearchItemHeaderType {
return .faq
case .messages:
return .messages
case .photos:
return .photos
case .links:
return .links
case .files:
@ -135,6 +140,7 @@ private enum ChatListSearchItemHeaderId: Int32 {
case chatTypes
case faq
case messages
case photos
case links
case files
case music

View File

@ -54,6 +54,7 @@ static_library(
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/GridMessageSelectionNode:GridMessageSelectionNode",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -54,6 +54,7 @@ swift_library(
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/ShareController:ShareController",
"//submodules/GridMessageSelectionNode:GridMessageSelectionNode",
],
visibility = [
"//visibility:public",

View File

@ -1683,10 +1683,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
if let searchContentNode = strongSelf.searchContentNode {
var updatedSearchOptionsImpl: ((ChatListSearchOptions?) -> Void)?
var updatedSearchOptionsImpl: ((ChatListSearchOptions?, Bool) -> Void)?
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedSearchOptions: { options in
updatedSearchOptionsImpl?(options)
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedSearchOptions: { options, hasDate in
updatedSearchOptionsImpl?(options, hasDate)
}) {
let (filterContainerNode, activate) = filterContainerNodeAndActivate
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true)
@ -1695,12 +1695,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
activate()
updatedSearchOptionsImpl = { [weak self, weak filterContainerNode] options in
updatedSearchOptionsImpl = { [weak self, weak filterContainerNode] options, hasDate in
guard let strongSelf = self, let strongFilterContainerNode = filterContainerNode else {
return
}
var node: ASDisplayNode?
if let options = options, options.messageTags != nil {
if let options = options, options.messageTags != nil && !hasDate {
} else {
node = strongFilterContainerNode
}

View File

@ -1147,7 +1147,7 @@ final class ChatListControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)?) -> (ASDisplayNode, () -> Void)? {
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)?) -> (ASDisplayNode, () -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil
}
@ -1169,8 +1169,8 @@ final class ChatListControllerNode: ASDisplayNode {
self?.controller?.present(c, in: .window(.root), with: a)
}, presentInGlobalOverlay: { [weak self] c, a in
self?.controller?.presentInGlobalOverlay(c, with: a)
}, navigationController: navigationController, updatedSearchOptions: { options in
updatedSearchOptions?(options)
}, navigationController: navigationController, updatedSearchOptions: { options, hasDate in
updatedSearchOptions?(options, hasDate)
})
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in

View File

@ -48,6 +48,12 @@ private enum ChatListRecentEntryStableId: Hashable {
case peerId(PeerId)
}
private enum ChatListTokenId: Int32 {
case filter
case peer
case date
}
private enum ChatListRecentEntry: Comparable, Identifiable {
case topPeers([Peer], PresentationTheme, PresentationStrings)
case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)
@ -275,7 +281,7 @@ public enum ChatListSearchSectionExpandType {
public enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32, Bool?, Bool)
case addContact(String, PresentationTheme, PresentationStrings)
public var stableId: ChatListSearchEntryStableId {
@ -284,7 +290,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _, _):
case let .message(message, _, _, _, _, _, _):
return .messageId(message.id)
case .addContact:
return .addContact
@ -305,8 +311,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount) = rhs {
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
@ -325,6 +331,12 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if lhsTotalCount != rhsTotalCount {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsDisplayCustomHeader != rhsDisplayCustomHeader {
return false
}
return true
} else {
return false
@ -364,8 +376,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case .message, .addContact:
return true
}
case let .message(lhsMessage, _, _, _, _):
if case let .message(rhsMessage, _, _, _, _) = rhs {
case let .message(lhsMessage, _, _, _, _, _, _):
if case let .message(rhsMessage, _, _, _, _, _, _) = rhs {
return lhsMessage.index < rhsMessage.index
} else if case .addContact = rhs {
return true
@ -516,12 +528,14 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
peerContextAction(peer.peer, .search, node, gesture)
}
})
case let .message(message, peer, readState, presentationData, totalCount):
case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader):
let header: ChatListSearchItemHeader?
if enableHeaders {
if false, enableHeaders && displayCustomHeader {
let type: ChatListSearchItemHeaderType
if let searchOptions = searchOptions {
if searchOptions.messageTags == .webPage {
if searchOptions.messageTags == .photoOrVideo {
type = .photos(totalCount)
} else if searchOptions.messageTags == .webPage {
type = .links(totalCount)
} else if searchOptions.messageTags == .file {
type = .files(totalCount)
@ -538,8 +552,14 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
header = nil
}
let selection: ChatHistoryMessageSelection
if let selected = selected {
selection = .selectable(selected: selected)
} else {
selection = .none
}
if let tags = searchOptions?.messageTags, tags != .photoOrVideo {
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: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: .none, displayHeader: false, customHeader: header, isGlobalSearchResult: true)
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: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: displayCustomHeader ? header : nil, isGlobalSearchResult: true)
} else {
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
@ -597,32 +617,32 @@ public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatLis
private struct ChatListSearchContainerNodeState: Equatable {
let peerIdWithRevealedOptions: PeerId?
let selectedMessageIds: Set<MessageId>?
init(peerIdWithRevealedOptions: PeerId? = nil, selectedMessageIds: Set<MessageId>? = nil) {
init(peerIdWithRevealedOptions: PeerId? = nil) {
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.selectedMessageIds = selectedMessageIds
}
static func ==(lhs: ChatListSearchContainerNodeState, rhs: ChatListSearchContainerNodeState) -> Bool {
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions || lhs.selectedMessageIds != rhs.selectedMessageIds {
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions {
return false
}
return true
}
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChatListSearchContainerNodeState {
return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: peerIdWithRevealedOptions, selectedMessageIds: self.selectedMessageIds)
}
func withUpdatedSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?) -> ChatListSearchContainerNodeState {
return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, selectedMessageIds: selectedMessageIds)
return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
}
}
private struct ChatListSearchContainerNodeSearchState: Equatable {
var expandLocalSearch: Bool = false
var expandGlobalSearch: Bool = false
var selectedMessageIds: Set<MessageId>?
func withUpdatedSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?) -> ChatListSearchContainerNodeSearchState {
return ChatListSearchContainerNodeSearchState(expandLocalSearch: self.expandLocalSearch, expandGlobalSearch: self.expandGlobalSearch, selectedMessageIds: selectedMessageIds)
}
}
private func doesPeerMatchFilter(peer: Peer, filter: ChatListNodePeersFilter) -> Bool {
@ -741,14 +761,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
private var dismissingPanel: ASDisplayNode?
private let updatedSearchOptions: ((ChatListSearchOptions?) -> Void)?
private let updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)?
private let emptyResultsTitleNode: ImmediateTextNode
private let emptyResultsTextNode: ImmediateTextNode
private let emptyResultsAnimationNode: AnimatedStickerNode
private var animationSize: CGSize = CGSize()
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) {
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)? = nil) {
self.context = context
self.peersFilter = filter
self.dimNode = ASDisplayNode()
@ -770,6 +790,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)?
var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?
var toggleMessageSelectionImpl: ((MessageId, Bool) -> Void)?
var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)?
var addToTransitionSurfaceImpl: ((UIView) -> Void)?
@ -777,6 +798,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openMediaMessageImpl?(message, mode)
}, messageContextAction: { message, sourceNode, sourceRect, gesture in
messageContextActionImpl?(message, sourceNode, sourceRect, gesture)
}, toggleMessageSelection: { messageId, selected in
toggleMessageSelectionImpl?(messageId, selected)
})
self.listNode = ListView()
@ -1140,20 +1163,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
peer = RenderedPeer(peer: channelPeer)
}
}
entries.append(.message(message, peer, nil, presentationData, 1))
entries.append(.message(message, peer, nil, presentationData, 1, nil, true))
index += 1
}
var firstHeaderId: Int64?
if !foundRemotePeers.2 {
index = 0
for message in foundRemoteMessages.0.0 {
let headerId = listMessageDateHeaderId(timestamp: message.timestamp)
if firstHeaderId == nil {
firstHeaderId = headerId
}
var peer = RenderedPeer(message: message)
if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference {
if let channelPeer = message.peers[migrationReference.peerId] {
peer = RenderedPeer(peer: channelPeer)
}
}
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2))
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2, searchState.selectedMessageIds?.contains(message.id), headerId == firstHeaderId))
index += 1
}
}
@ -1191,7 +1219,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
openMediaMessageImpl = { [weak self] message, mode in
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: {
self?.view.window?.endEditing(true)
self?.dismissInput()
}, present: { c, a in
present(c, a)
}, transitionNode: { messageId, media in
@ -1199,7 +1227,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}, addToTransitionSurface: { view in
addToTransitionSurfaceImpl?(view)
}, openUrl: { [weak self] url in
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { [weak self] c in
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { c in
present(c, nil)
}, openResolved: { [weak self] resolved in
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
@ -1209,7 +1237,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
present: { c, a in
present(c, a)
}, dismissInput: {
self?.view.window?.endEditing(true)
self?.dismissInput()
}, contentContext: nil)
})
}, openPeer: { peer, navigation in
@ -1226,6 +1254,20 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
strongSelf.messageContextActions(message, node: sourceNode, rect: sourceRect, gesture: gesture)
}
}
toggleMessageSelectionImpl = { [weak self] messageId, selected in
if let strongSelf = self {
strongSelf.updateSearchState { state in
var selectedMessageIds = state.selectedMessageIds ?? Set()
if selected {
selectedMessageIds.insert(messageId)
} else {
selectedMessageIds.remove(messageId)
}
return state.withUpdatedSelectedMessageIds(selectedMessageIds)
}
}
}
transitionNodeImpl = { [weak self] messageId, media in
if let strongSelf = self {
@ -1261,7 +1303,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { [weak self] peer, _ in
self?.view.endEditing(true)
self?.dismissInput()
openPeer(peer, false)
let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start()
self?.listNode.clearHighlightAnimated(true)
@ -1269,14 +1311,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}, togglePeerSelected: { _ in
}, additionalCategorySelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
self?.view.endEditing(true)
self?.dismissInput()
if let peer = message.peers[message.id.peerId] {
openMessage(peer, message.id)
}
self?.listNode.clearHighlightAnimated(true)
}, groupSelected: { _ in
}, addContact: { [weak self] phoneNumber in
self?.view.endEditing(true)
self?.dismissInput()
addContact?(phoneNumber)
self?.listNode.clearHighlightAnimated(true)
}, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in
@ -1416,37 +1458,47 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}))
let listInteraction = ListMessageItemInteraction(openMessage: { [weak self] message, mode -> Bool in
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in
self?.view.window?.endEditing(true)
}, present: { c, a in
present(c, a)
}, transitionNode: { [weak self] messageId, media in
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let strongSelf = self {
strongSelf.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ListMessageNode {
if let result = itemNode.transitionNode(id: messageId, media: media) {
transitionNode = result
self?.dismissInput()
let _ = (foundMessages
|> take(1)
|> deliverOnMainQueue).start(next: { m in
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in
self?.dismissInput()
}, present: { c, a in
present(c, a)
}, transitionNode: { [weak self] messageId, media in
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let strongSelf = self {
strongSelf.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ListMessageNode {
if let result = itemNode.transitionNode(id: messageId, media: media) {
transitionNode = result
}
}
}
}
}
return transitionNode
}, addToTransitionSurface: { view in
self?.view.addSubview(view)
}, openUrl: { url in
// self?.openUrl(url: url, concealed: false, external: false)
}, openPeer: { peer, navigation in
// self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { _, _ in
}, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .searchResults(query: "", peerId: nil, messages: [], at: message.id), gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: {
loadMore()
})))
return transitionNode
}, addToTransitionSurface: { view in
self?.view.addSubview(view)
}, openUrl: { url in
// self?.openUrl(url: url, concealed: false, external: false)
}, openPeer: { peer, navigation in
// self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { _, _ in
}, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: {
loadMore()
}), gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: {
loadMore()
})))
})
return true
}, openMessageContextMenu: { [weak self] message, bool, node, rect, gesture in
self?.messageContextAction(message, node: node, rect: rect, gesture: gesture)
}, toggleMessagesSelection: { messageId, selected in
if let messageId = messageId.first {
toggleMessageSelectionImpl?(messageId, selected)
}
}, openUrl: { url, _, _, message in
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { c in
present(c, nil)
@ -1458,7 +1510,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
present: { c, a in
present(c, a)
}, dismissInput: {
self?.view.window?.endEditing(true)
self?.dismissInput()
}, contentContext: nil)
})
}, openInstantPage: { message, data in
@ -1467,7 +1519,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
navigationController?.pushViewController(pageController)
}
}, longTap: { action, message in
}, getHiddenMedia: {
return [:]
})
@ -1482,7 +1533,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var totalCount: Int32 = 0
if let entries = entriesAndFlags?.0 {
for entry in entries {
if case let .message(_, _, _, _, count) = entry {
if case let .message(_, _, _, _, count, _, _) = entry {
totalCount = count
break
}
@ -1525,7 +1576,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeerId(peer.id, peerName: peer.compactDisplayTitle), clearQuery: true)
strongSelf.dismissInput?()
}, searchResults: newEntries.compactMap { entry -> Message? in
if case let .message(message, _, _, _, _) = entry {
if case let .message(message, _, _, _, _, _, _) = entry {
return message
} else {
return nil
@ -1580,7 +1631,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
guard let strongSelf = self else {
return
}
let messageTags: MessageTags
let messageTags: MessageTags?
var maxDate: Int32? = strongSelf.currentSearchOptions.maxDate
var clearQuery: Bool = false
switch filter {
case .media:
messageTags = .photoOrVideo
@ -1590,8 +1643,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
messageTags = .file
case .music:
messageTags = .music
case let .date(date, _):
messageTags = strongSelf.currentSearchOptions.messageTags
maxDate = date
clearQuery = true
}
strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedMessageTags(messageTags))
strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedMessageTags(messageTags).withUpdatedMaxDate(maxDate), clearQuery: clearQuery)
}
self.mediaStatusDisposable = (combineLatest(context.sharedContext.mediaManager.globalMediaPlayerState, self.searchOptions.get())
@ -1599,7 +1656,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if let (account, state, type) = playlistStateAndType {
switch state {
case let .state(state):
if let playlistId = state.playlistId as? PeerMessagesMediaPlaylistId, case .searchResults = playlistId {
if let playlistId = state.playlistId as? PeerMessagesMediaPlaylistId, case .custom = playlistId {
if case .music = type, searchOptions?.messageTags == .music {
return .single((account, state, type))
} else {
@ -1664,8 +1721,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
public override func searchTokensUpdated(tokens: [SearchBarToken]) {
var updatedOptions = self.searchOptionsValue
if updatedOptions?.peerId != nil {
updatedOptions = updatedOptions?.withUpdatedPeerId(nil, peerName: nil)
var tokensIdSet = Set<AnyHashable>()
for token in tokens {
tokensIdSet.insert(token.id)
}
if !tokensIdSet.contains(ChatListTokenId.filter.rawValue) && updatedOptions?.messageTags != nil {
updatedOptions = updatedOptions?.withUpdatedMessageTags(nil)
}
if !tokensIdSet.contains(ChatListTokenId.date.rawValue) && updatedOptions?.maxDate != nil {
updatedOptions = updatedOptions?.withUpdatedMaxDate(nil)
}
if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peerId != nil {
updatedOptions = updatedOptions?.withUpdatedPeerId(nil, peerName: nil)
}
self.updateSearchOptions(updatedOptions)
}
@ -1693,12 +1760,22 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
if let title = title {
tokens.append(SearchBarToken(id: messageTags.rawValue, icon: icon, title: title))
tokens.append(SearchBarToken(id: ChatListTokenId.filter.rawValue, icon: icon, title: title))
}
}
if let peerId = options?.peerId, let peerName = options?.peerName {
tokens.append(SearchBarToken(id: peerId, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peerName))
if let _ = options?.peerId, let peerName = options?.peerName {
tokens.append(SearchBarToken(id: ChatListTokenId.peer.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peerName))
}
if let maxDate = options?.maxDate {
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .medium
let title = formatter.string(from: Date(timeIntervalSince1970: Double(maxDate)))
tokens.append(SearchBarToken(id: ChatListTokenId.date.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Calendar"), title: title))
self.possibleDate = nil
}
if clearQuery {
@ -1715,7 +1792,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
self.updatedSearchOptions?(options)
self.updatedSearchOptions?(options, self.possibleDate != nil)
}
private func updateTheme(theme: PresentationTheme) {
@ -1750,14 +1827,38 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.searchStateValue = state
self.searchStatePromise.set(state)
}
self.mediaNode.selectedMessageIds = self.searchStateValue.selectedMessageIds
self.mediaNode.updateSelectedMessages(animated: true)
self.selectionPanelNode?.selectedMessages = self.searchStateValue.selectedMessageIds ?? []
}
var possibleDate: Date?
override public func searchTextUpdated(text: String) {
let searchQuery: String? = !text.isEmpty ? text : nil
self.interaction?.searchTextHighightState = searchQuery
self.searchQuery.set(.single(searchQuery))
self.searchQueryValue = searchQuery
let previousPossibleDate = self.possibleDate
do {
let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
if let match = dd.firstMatch(in: text, options: [], range: NSMakeRange(0, text.utf16.count)) {
self.possibleDate = match.date
} else {
self.possibleDate = nil
}
}
catch {
self.possibleDate = nil
}
if previousPossibleDate != self.possibleDate {
self.updatedSearchOptions?(self.searchOptionsValue, self.possibleDate != nil)
if let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
if text.isEmpty {
self.updateSearchState { state in
var state = state
@ -1821,7 +1922,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
strongSelf.mediaNode.isHidden = true
}
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription(transition.query).0, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
let emptyResultsText: String
if transition.query.isEmpty {
emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).0
} else {
emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription
}
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: emptyResultsText, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
let emptyResults = displayingResults && transition.isEmpty
strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults
@ -1991,7 +2098,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account)
}
let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: true, parentNavigationController: strongSelf.navigationController)
strongSelf.view.window?.endEditing(true)
strongSelf.dismissInput()
strongSelf.interaction?.present(controller)
} else if index.1 {
if !progressStarted {
@ -2052,10 +2159,22 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 37.0)))
self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 37.0), sideInset: layout.safeInsets.left, filters: ChatListSearchFilter.allCases.map { .filter($0) }, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
let filters: [ChatListSearchFilter]
if let possibleDate = self.possibleDate {
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .medium
let title = formatter.string(from: possibleDate)
filters = [.date(Int32(possibleDate.timeIntervalSince1970), title)]
} else {
filters = [.media, .links, .files, .music]
}
self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 37.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
if let selectedMessageIds = self.stateValue.selectedMessageIds {
if let selectedMessageIds = self.searchStateValue.selectedMessageIds {
var wasAdded = false
let selectionPanelNode: ChatListSearchMessageSelectionPanelNode
if let current = self.selectionPanelNode {
@ -2068,7 +2187,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
strongSelf.deleteMessages(messageIds: nil)
}, shareMessages: { [weak self] in
guard let strongSelf = self, let messageIds = strongSelf.stateValue.selectedMessageIds, !messageIds.isEmpty else {
guard let strongSelf = self, let messageIds = strongSelf.searchStateValue.selectedMessageIds, !messageIds.isEmpty else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in
@ -2085,7 +2204,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in
return lhs.index < rhs.index
})), externalShare: true, immediateExternalShare: true)
strongSelf.view.endEditing(true)
strongSelf.dismissInput()
strongSelf.present?(shareController, nil)
}
})
@ -2213,7 +2332,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
actionSheet?.dismissAnimated()
})
])])
self.view.window?.endEditing(true)
self.dismissInput()
self.interaction?.present(actionSheet)
}
@ -2259,7 +2378,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
if let strongSelf = self {
strongSelf.updateState { state in
strongSelf.dismissInput()
strongSelf.updateSearchState { state in
return state.withUpdatedSelectedMessageIds([message.id])
}
@ -2329,7 +2450,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.updateState { state in
strongSelf.dismissInput()
strongSelf.updateSearchState { state in
return state.withUpdatedSelectedMessageIds([message.id])
}
@ -2349,48 +2472,54 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.setQuery?(nil, [], self.searchQueryValue ?? "")
}
func deleteMessages(messageIds: Set<MessageId>?) {
let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds
}
func forwardMessages(messageIds: Set<MessageId>?) {
let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds
if let messageIds = messageIds, !messageIds.isEmpty {
let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled]))
peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in
if let strongSelf = self, let _ = peerSelectionController {
if peerId == strongSelf.context.account.peerId {
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
return .forward(source: id, grouping: .auto, attributes: [])
})
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
if let strongSelf = self {
let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
guard let id = id else {
return nil
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
if let strongSelf = self {
let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
guard let id = id else {
return nil
}
return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|> mapToSignal { status, _ -> Signal<Bool, NoError> in
if status != nil {
return .never()
} else {
return .single(true)
}
}
return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|> mapToSignal { status, _ -> Signal<Bool, NoError> in
if status != nil {
return .never()
} else {
return .single(true)
}
}
|> take(1)
})
strongSelf.activeActionDisposable.set((combineLatest(signals)
|> deliverOnMainQueue).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.present?(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), nil)
}))
}
})
|> take(1)
})
strongSelf.activeActionDisposable.set((combineLatest(signals)
|> deliverOnMainQueue).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.present?(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), nil)
}))
}
})
if let peerSelectionController = peerSelectionController {
peerSelectionController.dismiss()
}
strongSelf.updateSearchState { state in
return state.withUpdatedSelectedMessageIds(nil)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
} else {
let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
@ -2404,20 +2533,19 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if let strongSelf = self {
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
let ready = Promise<Bool>()
let signal = ready.get()
|> filter({ $0 })
|> take(1)
|> deliverOnMainQueue
strongSelf.activeActionDisposable.set(signal.start(next: { _ in
let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, botStart: nil, mode: .standard(previewing: false))
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: {
if let peerSelectionController = peerSelectionController {
peerSelectionController.dismiss()
}
}))
let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, botStart: nil, mode: .standard(previewing: false))
strongSelf.navigationController?.replaceTopController(controller, animated: false, ready: ready)
})
strongSelf.updateSearchState { state in
return state.withUpdatedSelectedMessageIds(nil)
}
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
})
}
@ -2426,6 +2554,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.navigationController?.pushViewController(peerSelectionController)
}
}
private func dismissInput() {
self.view.window?.endEditing(true)
}
}
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {

View File

@ -7,11 +7,27 @@ import Postbox
import TelegramCore
import TelegramPresentationData
enum ChatListSearchFilter: Int32, Hashable, CaseIterable {
enum ChatListSearchFilter: Equatable {
case media
case links
case files
case music
case date(Int32, String)
var id: Int32 {
switch self {
case .media:
return 0
case .links:
return 1
case .files:
return 2
case .music:
return 3
case let .date(date, _):
return date
}
}
}
private final class ItemNode: ASDisplayNode {
@ -91,6 +107,9 @@ private final class ItemNode: ASDisplayNode {
case .music:
title = presentationData.strings.ChatList_Search_FilterMusic
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Music"), color: color)
case let .date(_, dateTitle):
title = dateTitle
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Calendar"), color: color)
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color)
@ -131,7 +150,7 @@ enum ChatListSearchFilterEntry: Equatable {
var id: ChatListSearchFilterEntryId {
switch self {
case let .filter(filter):
return .filter(filter.rawValue)
return .filter(filter.id)
}
}
}
@ -245,7 +264,7 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
}
longTitlesWidth += resolvedSideInset
if longTitlesWidth < size.width {
if longTitlesWidth < size.width && tabSizes.count > 3 {
spacing = (size.width - titlesWidth - resolvedSideInset * 2.0) / CGFloat(tabSizes.count - 1)
}

View File

@ -15,6 +15,7 @@ import ListMessageItem
import ListSectionHeaderNode
import ChatMessageInteractiveMediaBadge
import ShimmerEffect
import GridMessageSelectionNode
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
private let mediaBadgeTextColor = UIColor.white
@ -50,6 +51,7 @@ private final class VisualMediaItemNode: ASDisplayNode {
private var statusNode: RadialStatusNode
private let mediaBadgeNode: ChatMessageInteractiveMediaBadge
private var placeholderNode: ShimmerEffectNode?
private var selectionNode: GridMessageSelectionNode?
private let fetchStatusDisposable = MetaDisposable()
private let fetchDisposable = MetaDisposable()
@ -298,6 +300,8 @@ private final class VisualMediaItemNode: ASDisplayNode {
self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0))
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size)
self.updateHiddenMedia()
if let placeholderNode = self.placeholderNode {
@ -350,8 +354,46 @@ private final class VisualMediaItemNode: ASDisplayNode {
}
func updateSelectionState(animated: Bool) {
if let (item, _, _, _) = self.item, let theme = self.theme {
if let (item, _, _, _) = self.item, let theme = self.theme, let message = item.message {
self.containerNode.isGestureEnabled = self.interaction.selectedMessageIds == nil
if let selectedIds = self.interaction.selectedMessageIds {
let selected = selectedIds.contains(message.id)
if let selectionNode = self.selectionNode {
selectionNode.updateSelected(selected, animated: animated)
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
} else {
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
if let strongSelf = self, let messageId = strongSelf.item?.0.message?.id {
var toggledValue = true
if let selectedMessageIds = strongSelf.interaction.selectedMessageIds, selectedMessageIds.contains(messageId) {
toggledValue = false
}
strongSelf.interaction.toggleSelection(messageId, toggledValue)
}
})
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
self.containerNode.addSubnode(selectionNode)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)
if animated {
selectionNode.animateIn()
}
}
} else {
if let selectionNode = self.selectionNode {
self.selectionNode = nil
if animated {
selectionNode.animateOut { [weak selectionNode] in
selectionNode?.removeFromSupernode()
}
} else {
selectionNode.removeFromSupernode()
}
}
}
}
}
@ -637,7 +679,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
public var beganInteractiveDragging: (() -> Void)?
public var loadMore: (() -> Void)?
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) {
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void, toggleMessageSelection: @escaping (MessageId, Bool) -> Void) {
self.context = context
self.contentType = contentType
@ -650,17 +692,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
super.init()
self._itemInteraction = VisualMediaItemInteraction(
openMessage: { [weak self] message in
openMessage: { message in
let _ = openMessage(message, .default)
},
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in
openMessageContextActions: { message, sourceNode, sourceRect, gesture in
messageContextAction(message, sourceNode, sourceRect, gesture)
},
toggleSelection: { [weak self] id, value in
// self?.chatControllerInteraction.toggleMessagesSelection([id], value)
toggleSelection: { id, value in
toggleMessageSelection(id, value)
}
)
// self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
@ -672,7 +713,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.view.delegate = self
self.addSubnode(self.scrollNode)
self.addSubnode(self.headerNode)
self.addSubnode(self.floatingHeaderNode)
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
@ -709,7 +749,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
if let entries = entries {
loading = false
for entry in entries {
if case let .message(message, _, _, _, _) = entry {
if case let .message(message, _, _, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message))
}
}
@ -787,8 +827,13 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.view.addSubview(view)
}
var selectedMessageIds: Set<MessageId>? {
didSet {
self.itemInteraction.selectedMessageIds = self.selectedMessageIds
}
}
func updateSelectedMessages(animated: Bool) {
// self.itemInteraction.selectedMessageIds = self.chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
for (_, itemNode) in self.visibleMediaItems {
itemNode.updateSelectionState(animated: animated)
}
@ -797,9 +842,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
self.currentParams = (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData)
let headerSize = CGSize(width: size.width, height: 28.0)
self.headerNode.frame = CGRect(origin: CGPoint(), size: headerSize)
self.headerNode.updateLayout(size: headerSize, leftInset: sideInset, rightInset: sideInset)
let headerSize = CGSize(width: size.width, height: 0.0)
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: headerSize.height), size: CGSize(width: size.width, height: size.height - headerSize.height)))
@ -926,7 +969,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
if let headerItem = headerItem {
let (year, month) = listMessageDateHeaderInfo(timestamp: headerItem.timestamp)
let headerSize = self.floatingHeaderNode.update(constrainedWidth: size.width, year: year, month: month, theme: theme, strings: strings)
self.floatingHeaderNode.frame = CGRect(origin: CGPoint(x: floor((size.width - headerSize.width) / 2.0), y: 7.0 + 28.0), size: headerSize)
self.floatingHeaderNode.frame = CGRect(origin: CGPoint(x: floor((size.width - headerSize.width) / 2.0), y: 7.0), size: headerSize)
self.floatingHeaderNode.isHidden = false
} else {
self.floatingHeaderNode.isHidden = true

View File

@ -96,6 +96,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.deleteButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.shareButton)
@ -159,7 +160,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom
let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom - 49.0
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
@ -172,10 +173,10 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
}
@objc func forwardButtonPressed() {
self.shareMessages()
self.forwardMessages()
}
@objc func shareButtonPressed() {
self.forwardMessages()
self.shareMessages()
}
}

View File

@ -46,7 +46,7 @@ public final class HashtagSearchController: TelegramBaseController {
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount) })
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount, nil, false) })
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { _, _ in

View File

@ -572,7 +572,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - dateNodeLayout.size.width, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - dateNodeLayout.size.width - 4.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -28,14 +28,28 @@ private func generateBackground(foregroundColor: UIColor, diameter: CGFloat) ->
public struct SearchBarToken {
public struct Style {
public let backgroundColor: UIColor
public let foregroundColor: UIColor
public let strokeColor: UIColor
public init(backgroundColor: UIColor, foregroundColor: UIColor, strokeColor: UIColor) {
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.strokeColor = strokeColor
}
}
public let id: AnyHashable
public let icon: UIImage?
public let title: String
public let style: Style?
public init(id: AnyHashable, icon: UIImage?, title: String) {
public init(id: AnyHashable, icon: UIImage?, title: String, style: Style? = nil) {
self.id = id
self.icon = icon
self.title = title
self.style = style
}
}
@ -49,6 +63,8 @@ private final class TokenNode: ASDisplayNode {
var isSelected: Bool = false
var isCollapsed: Bool = false
var tapped: (() -> Void)?
init(theme: SearchBarNodeTheme, token: SearchBarToken) {
self.theme = theme
self.token = token
@ -62,7 +78,6 @@ private final class TokenNode: ASDisplayNode {
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: theme.inputIcon)
super.init()
@ -70,18 +85,44 @@ private final class TokenNode: ASDisplayNode {
self.addSubnode(self.backgroundNode)
self.iconNode.image = generateTintedImage(image: token.icon, color: .white)
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.addSubnode(self.iconNode)
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: .white)
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
self.addSubnode(self.titleNode)
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture)))
}
@objc private func tapGesture() {
self.tapped?()
}
func update(theme: SearchBarNodeTheme, token: SearchBarToken, isSelected: Bool, isCollapsed: Bool) {
let wasSelected = self.isSelected
self.isSelected = isSelected
self.isCollapsed = isCollapsed
if theme !== self.theme {
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: isSelected ? self.theme.accent : self.theme.inputIcon)
if theme !== self.theme || isSelected != wasSelected {
let backgroundColor = isSelected ? self.theme.accent : (token.style?.backgroundColor ?? self.theme.inputIcon)
let strokeColor = isSelected ? backgroundColor : (token.style?.strokeColor ?? backgroundColor)
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
let foregroundColor = isSelected ? .white : (token.style?.foregroundColor ?? .white)
if let image = token.icon {
self.iconNode.image = generateTintedImage(image: image, color: foregroundColor)
}
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
}
}
@ -124,14 +165,47 @@ private class SearchBarTextField: UITextField {
var tokenNodes: [AnyHashable: TokenNode] = [:]
var tokens: [SearchBarToken] = [] {
didSet {
self._selectedTokenIndex = nil
self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut))
self.setNeedsLayout()
self.updateCursorColor()
}
}
var _selectedTokenIndex: Int?
var selectedTokenIndex: Int? {
didSet {
get {
return self._selectedTokenIndex
}
set {
_selectedTokenIndex = newValue
self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut))
self.setNeedsLayout()
self.updateCursorColor()
}
}
private func updateCursorColor() {
if self._selectedTokenIndex != nil {
super.tintColor = UIColor.clear
} else {
super.tintColor = self._tintColor
}
}
var _tintColor: UIColor = .black
override var tintColor: UIColor! {
get {
return super.tintColor
}
set {
if newValue != UIColor.clear {
self._tintColor = newValue
if self.selectedTokenIndex == nil {
super.tintColor = newValue
}
}
}
}
@ -142,15 +216,19 @@ private class SearchBarTextField: UITextField {
let token = self.tokens[i]
let tokenNode: TokenNode
var tokenNodeTransition = transition
if let current = self.tokenNodes[token.id] {
tokenNode = current
} else {
tokenNodeTransition = .immediate
tokenNode = TokenNode(theme: self.theme, token: token)
self.tokenNodes[token.id] = tokenNode
}
tokenNode.update(theme: self.theme, token: token, isSelected: false, isCollapsed: i < self.tokens.count - 1)
tokenNode.tapped = { [weak self] in
self?.selectedTokenIndex = i
self?.becomeFirstResponder()
}
let isSelected = i == self.selectedTokenIndex
let isCollapsed = !isSelected && i < self.tokens.count - 1
tokenNode.update(theme: self.theme, token: token, isSelected: isSelected, isCollapsed: isCollapsed)
}
var removeKeys: [AnyHashable] = []
for (id, _) in self.tokenNodes {
@ -350,7 +428,16 @@ private class SearchBarTextField: UITextField {
}
override func deleteBackward() {
if self.text == nil || self.text!.isEmpty {
var processed = false
if let selectedRange = self.selectedTextRange {
let cursorPosition = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
if cursorPosition == 0 && !self.tokens.isEmpty && self.selectedTokenIndex == nil {
self.selectedTokenIndex = self.tokens.count - 1
processed = true
}
}
if !processed && (self.text == nil || self.text!.isEmpty) {
self.didDeleteBackwardWhileEmpty?()
}
super.deleteBackward()
@ -624,8 +711,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
guard let strongSelf = self else {
return
}
if strongSelf.tokens.count > 1 {
strongSelf.tokens.removeLast()
if let index = strongSelf.textField.selectedTokenIndex {
strongSelf.tokens.remove(at: index)
strongSelf.tokensUpdated?(strongSelf.tokens)
} else {
strongSelf.clearPressed()
@ -637,7 +724,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.updateThemeAndStrings(theme: theme, strings: strings)
}
public func updateThemeAndStrings(theme: SearchBarNodeTheme, strings: PresentationStrings) {
if self.theme != theme || self.strings !== strings {
self.cancelButton.setAttributedTitle(NSAttributedString(string: self.cancelText ?? strings.Common_Cancel, font: self.cancelText != nil ? Font.semibold(17.0) : Font.regular(17.0), textColor: theme.accent), for: [])
@ -864,11 +951,18 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
@objc private func textFieldDidChange(_ textField: UITextField) {
self.updateIsEmpty()
if let _ = self.textField.selectedTokenIndex {
self.textField.selectedTokenIndex = nil
}
if let textUpdated = self.textUpdated {
textUpdated(textField.text ?? "", textField.textInputMode?.primaryLanguage)
}
}
public func textFieldDidEndEditing(_ textField: UITextField) {
self.textField.selectedTokenIndex = nil
}
public func selectAll() {
self.textField.becomeFirstResponder()
self.textField.selectAll(nil)

View File

@ -9,35 +9,6 @@ import SyncCore
import TelegramPresentationData
import ListSectionHeaderNode
private func nodeColor(for color: WallpaperSearchColor) -> UIColor {
switch color {
case .blue:
return UIColor(rgb: 0x0076ff)
case .red:
return UIColor(rgb: 0xff0000)
case .orange:
return UIColor(rgb: 0xff8a00)
case .yellow:
return UIColor(rgb: 0xffca00)
case .green:
return UIColor(rgb: 0x00e432)
case .teal:
return UIColor(rgb: 0x1fa9ab)
case .purple:
return UIColor(rgb: 0x7300aa)
case .pink:
return UIColor(rgb: 0xf9bec5)
case .brown:
return UIColor(rgb: 0x734021)
case .black:
return UIColor(rgb: 0x000000)
case .gray:
return UIColor(rgb: 0x5c585f)
case .white:
return UIColor(rgb: 0xffffff)
}
}
private class ThemeGridColorNode: HighlightableButtonNode {
let action: () -> Void
@ -54,7 +25,7 @@ private class ThemeGridColorNode: HighlightableButtonNode {
} else if color == .black && dark {
image = generateFilledCircleImage(diameter: 42.0, color: .black, strokeColor: strokeColor, strokeWidth: 1.0)
} else {
image = generateFilledCircleImage(diameter: 42.0, color: nodeColor(for: color))
image = generateFilledCircleImage(diameter: 42.0, color: color.displayColor)
}
self.setImage(image, for: .normal)
}

View File

@ -12,6 +12,7 @@ import AccountContext
import SearchUI
import ChatListSearchItemHeader
import WebSearchUI
import SearchBarNode
enum WallpaperSearchColor: CaseIterable {
case blue
@ -56,6 +57,35 @@ enum WallpaperSearchColor: CaseIterable {
}
}
var displayColor: UIColor {
switch self {
case .blue:
return UIColor(rgb: 0x0076ff)
case .red:
return UIColor(rgb: 0xff0000)
case .orange:
return UIColor(rgb: 0xff8a00)
case .yellow:
return UIColor(rgb: 0xffca00)
case .green:
return UIColor(rgb: 0x00e432)
case .teal:
return UIColor(rgb: 0x1fa9ab)
case .purple:
return UIColor(rgb: 0x7300aa)
case .pink:
return UIColor(rgb: 0xf9bec5)
case .brown:
return UIColor(rgb: 0x734021)
case .black:
return UIColor(rgb: 0x000000)
case .gray:
return UIColor(rgb: 0x5c585f)
case .white:
return UIColor(rgb: 0xffffff)
}
}
func localizedString(strings: PresentationStrings) -> String {
switch self {
case .blue:
@ -658,23 +688,30 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
self.queryPromise.set(.single(query))
if updateInterface {
let prefix: NSAttributedString?
let tokens: [SearchBarToken]
let text: String
let placeholder: String
switch query {
case let .generic(query):
prefix = nil
tokens = []
text = query
placeholder = self.presentationData.strings.Wallpaper_Search
case let .color(color, query):
let prefixString = NSMutableAttributedString()
prefixString.append(NSAttributedString(string: self.presentationData.strings.WallpaperSearch_ColorPrefix, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.inputTextColor))
prefixString.append(NSAttributedString(string: "\(color.localizedString(strings: self.presentationData.strings)) ", font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.accentColor))
prefix = prefixString
let backgroundColor = color.displayColor
let foregroundColor: UIColor
let strokeColor: UIColor
if color == .white {
foregroundColor = .black
strokeColor = self.presentationData.theme.rootController.navigationSearchBar.inputClearButtonColor
} else {
foregroundColor = .white
strokeColor = color.displayColor
}
tokens = [SearchBarToken(id: 0, icon: UIImage(bundleImageName: "Settings/WallpaperSearchColorIcon"), title: color.localizedString(strings: self.presentationData.strings), style: SearchBarToken.Style(backgroundColor: backgroundColor, foregroundColor: foregroundColor, strokeColor: strokeColor))]
text = query
placeholder = self.presentationData.strings.Wallpaper_SearchShort
}
self.setQuery?(prefix, [], text)
self.setQuery?(nil, tokens, text)
self.setPlaceholder?(placeholder)
}
}
@ -688,6 +725,10 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true)
}
override func searchTextClearTokens() {
self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true)
}
private func enqueueRecentTransition(_ transition: ThemeGridSearchContainerRecentTransition, firstTime: Bool) {
self.enqueuedRecentTransitions.append((transition, firstTime))

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_search_color.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -3631,15 +3631,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
transformedMessages = strongSelf.transformEnqueueMessages(messages)
}
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages)
|> deliverOnMainQueue).start(next: { messageIds in
if let strongSelf = self {
if strongSelf.presentationInterfaceState.isScheduledMessages {
} else {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
}
}
})
for message in transformedMessages {
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: [message])
|> deliverOnMainQueue).start(next: { messageIds in
})
}
// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages)
// |> deliverOnMainQueue).start(next: { messageIds in
// if let strongSelf = self {
// if strongSelf.presentationInterfaceState.isScheduledMessages {
// } else {
// strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
// }
// }
// })
donateSendMessageIntent(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, intentContext: .chat, peerIds: [peerId])
}

View File

@ -582,11 +582,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let fixedCombinedReadStates = Atomic<MessageHistoryViewReadState?>(value: nil)
var scheduled = false
var isScheduledMessages = false
if let subject = subject, case .scheduledMessages = subject {
scheduled = true
isScheduledMessages = true
}
var isAuxiliaryChat = scheduled
var isAuxiliaryChat = isScheduledMessages
if case .replyThread = chatLocation {
isAuxiliaryChat = true
}
@ -616,7 +616,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let historyViewUpdate = self.chatHistoryLocationPromise.get()
|> distinctUntilChanged
|> mapToSignal { location in
return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData)
return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: isScheduledMessages, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData)
|> beforeNext { viewUpdate in
switch viewUpdate {
case let .HistoryView(view, _, _, _, _, _, _):

View File

@ -54,6 +54,10 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self?.interaction.toggleMembersSearch(false)
}
self.searchBar.clearTokens = { [weak self] in
self?.interaction.toggleMembersSearch(false)
}
if let statuses = interaction.statuses {
self.searchingActivityDisposable = (statuses.searching
|> deliverOnMainQueue).start(next: { [weak self] value in
@ -90,6 +94,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
switch search.domain {
case .everything:
self.searchBar.tokens = []
self.searchBar.prefixString = nil
let placeholderText: String
switch self.chatLocation {
@ -98,13 +103,12 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
}
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
case .members:
self.searchBar.tokens = []
self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor)
self.searchBar.placeholderString = nil
case let .member(peer):
let prefixString = NSMutableAttributedString()
prefixString.append(NSAttributedString(string: self.strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor))
prefixString.append(NSAttributedString(string: "\(peer.compactDisplayTitle) ", font: searchBarFont, textColor: theme.rootController.navigationSearchBar.accentColor))
self.searchBar.prefixString = prefixString
self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peer.compactDisplayTitle)]
self.searchBar.prefixString = nil
self.searchBar.placeholderString = nil
}

View File

@ -164,7 +164,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: isGlobalSearch))
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: isGlobalSearch))
super.init()

View File

@ -140,6 +140,26 @@ private enum NavigatedMessageFromViewPosition {
case exact
}
private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: MessageIndex) -> [Message] {
guard let index = messages.firstIndex(where: { $0.index.id == centralIndex.id }) else {
return []
}
var result: [Message] = []
if index != 0 {
for i in (0 ..< index).reversed() {
result.append(messages[i])
break
}
}
if index != messages.count - 1 {
for i in index + 1 ..< messages.count {
result.append(messages[i])
break
}
}
return result
}
private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] {
guard let index = view.entries.firstIndex(where: { $0.index.id == centralIndex.id }) else {
return []
@ -160,6 +180,45 @@ private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: Mess
return result
}
private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? {
var index = 0
for message in messages {
if message.index.id == anchorIndex.id {
switch position {
case .exact:
return (message, aroundMessagesFromMessages(messages, centralIndex: message.index), true)
case .later:
if index + 1 < messages.count {
let message = messages[index + 1]
return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index + 1].index), true)
} else {
return nil
}
case .earlier:
if index != 0 {
let message = messages[index - 1]
return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index - 1].index), true)
} else {
return nil
}
}
}
index += 1
}
if !messages.isEmpty {
switch position {
case .later, .exact:
let message = messages[messages.count - 1]
return (message, aroundMessagesFromMessages(messages, centralIndex: messages[messages.count - 1].index), false)
case .earlier:
let message = messages[0]
return (message, aroundMessagesFromMessages(messages, centralIndex: messages[0].index), false)
}
} else {
return nil
}
}
private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? {
var index = 0
for entry in view.entries {
@ -286,19 +345,17 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
self.network = network
self.messagesLocation = location
if case .custom = location {
self.order = .reversed
}
switch self.messagesLocation {
case let .messages(_, _, messageId):
self.loadItem(anchor: .messageId(messageId), navigation: .later)
case let .singleMessage(messageId):
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _):
self.loadItem(anchor: .messageId(messageId), navigation: .later)
case let .recentActions(message):
self.loadingItem = false
self.currentItem = (message, [])
self.updateState()
case let .searchResults(_, _, messages, messageId):
self.loadingItem = false
self.currentItem = (messages.first(where: { $0.id == messageId })!, messages)
self.updateState()
}
}
@ -422,66 +479,81 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
switch anchor {
case let .messageId(messageId):
if case let .messages(peerId, tagMask, _) = self.messagesLocation {
let historySignal = self.postbox.messageAtId(messageId)
|> take(1)
|> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in
guard let message = message else {
return .single(nil)
}
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
return .single((message, aroundMessages))
} else {
return .single((message, []))
switch self.messagesLocation {
case let .messages(peerId, tagMask, _):
let historySignal = self.postbox.messageAtId(messageId)
|> take(1)
|> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in
guard let message = message else {
return .single(nil)
}
}
}
|> take(1)
|> deliverOnMainQueue
self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let (message, aroundMessages) = messageAndAroundMessages {
strongSelf.playbackStack.clear()
strongSelf.playbackStack.push(message.id)
strongSelf.currentItem = (message, aroundMessages)
strongSelf.playedToEnd = false
} else {
strongSelf.playedToEnd = true
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
return .single((message, aroundMessages))
} else {
return .single((message, []))
}
}
strongSelf.updateState()
}
}))
} else {
self.navigationDisposable.set((self.postbox.messageAtId(messageId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] message in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let message = message {
strongSelf.playbackStack.clear()
strongSelf.playbackStack.push(message.id)
strongSelf.currentItem = (message, [])
} else {
strongSelf.currentItem = nil
|> take(1)
|> deliverOnMainQueue
self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let (message, aroundMessages) = messageAndAroundMessages {
strongSelf.playbackStack.clear()
strongSelf.playbackStack.push(message.id)
strongSelf.currentItem = (message, aroundMessages)
strongSelf.playedToEnd = false
} else {
strongSelf.playedToEnd = true
}
strongSelf.updateState()
}
strongSelf.updateState()
}
}))
}))
case let .custom(messages, at, _):
self.navigationDisposable.set((messages
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] messages in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let message = messages.0.first(where: { $0.id == at }) {
strongSelf.playbackStack.clear()
strongSelf.playbackStack.push(message.id)
strongSelf.currentItem = (message, [])
} else {
strongSelf.currentItem = nil
}
strongSelf.updateState()
}
}))
default:
self.navigationDisposable.set((self.postbox.messageAtId(messageId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] message in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let message = message {
strongSelf.playbackStack.clear()
strongSelf.playbackStack.push(message.id)
strongSelf.currentItem = (message, [])
} else {
strongSelf.currentItem = nil
}
strongSelf.updateState()
}
}))
}
case let .index(index):
switch self.messagesLocation {
case let .searchResults(_, _, messages, _):
self.loadingItem = false
self.currentItem = (messages.first(where: { $0.id == index.id })!, messages)
self.updateState()
case let .messages(peerId, tagMask, _):
let inputIndex: Signal<MessageIndex, NoError>
let looping = self.looping
@ -599,6 +671,107 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
self.loadingItem = false
self.currentItem = (message, [])
self.updateState()
case let .custom(messages, _, loadMore):
let inputIndex: Signal<MessageIndex, NoError>
let looping = self.looping
switch self.order {
case .regular, .reversed:
inputIndex = .single(index)
case .random:
var playbackStack = self.playbackStack
inputIndex = self.postbox.transaction { transaction -> MessageIndex in
if case let .random(previous) = navigation, previous {
let _ = playbackStack.pop()
while true {
if let id = playbackStack.pop() {
if let message = transaction.getMessage(id) {
return message.index
}
} else {
break
}
}
}
return index
// return transaction.findRandomMessage(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: tagMask, ignoreIds: (playbackStack.ids, playbackStack.set)) ?? index
}
}
let historySignal = inputIndex
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
return messages
|> mapToSignal { messages, _, loadMore -> Signal<(Message, [Message])?, NoError> in
let position: NavigatedMessageFromViewPosition
switch navigation {
case .later:
position = .later
case .earlier:
position = .earlier
case .random:
position = .exact
}
if let (message, aroundMessages, exact) = navigatedMessageFromMessages(messages, anchorIndex: inputIndex, position: position) {
switch navigation {
case .random:
return .single((message, []))
default:
if exact {
return .single((message, aroundMessages))
}
}
}
if case .all = looping {
let viewIndex: HistoryViewInputAnchor
if case .earlier = navigation {
viewIndex = .upperBound
} else {
viewIndex = .lowerBound
}
return .single(nil)
// return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
// |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
// let position: NavigatedMessageFromViewPosition
// switch navigation {
// case .later, .random:
// position = .earlier
// case .earlier:
// position = .later
// }
// if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: MessageIndex.absoluteLowerBound(), position: position) {
// return .single((message, aroundMessages))
// } else {
// return .single(nil)
// }
// }
} else {
return .single(nil)
}
return .complete()
}
}
|> take(1)
|> deliverOnMainQueue
self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in
if let strongSelf = self {
assert(strongSelf.loadingItem)
strongSelf.loadingItem = false
if let (message, aroundMessages) = messageAndAroundMessages {
if case let .random(previous) = navigation, previous {
strongSelf.playbackStack.resetToId(message.id)
} else {
strongSelf.playbackStack.push(message.id)
}
strongSelf.currentItem = (message, aroundMessages)
strongSelf.playedToEnd = false
} else {
strongSelf.playedToEnd = true
}
strongSelf.updateState()
}
}))
}
}
}