mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Search filters improvements
This commit is contained in:
parent
cbf67b1deb
commit
2864cb2987
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -54,6 +54,7 @@ swift_library(
|
||||
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
|
||||
"//submodules/ChatInterfaceState:ChatInterfaceState",
|
||||
"//submodules/ShareController:ShareController",
|
||||
"//submodules/GridMessageSelectionNode:GridMessageSelectionNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_search_color.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/ic_search_color.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/ic_search_color.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -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])
|
||||
}
|
||||
|
@ -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, _, _, _, _, _, _):
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user