mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-29 06:30:51 +00:00
Load more search results while scrolling
This commit is contained in:
parent
f60ade542a
commit
b4e64ca75a
@ -187,7 +187,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let searchResult = Promise<(SearchMessagesResult, SearchMessagesState)?>()
|
private let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
|
||||||
private let loadingMessage = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let loadingMessage = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
|
||||||
private var preloadHistoryPeerId: PeerId?
|
private var preloadHistoryPeerId: PeerId?
|
||||||
@ -3227,9 +3227,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let _ = (strongSelf.searchResult.get()
|
let _ = (strongSelf.searchResult.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] searchResult in
|
|> deliverOnMainQueue).start(next: { [weak self] searchResult in
|
||||||
if let strongSelf = self, let searchResult = searchResult?.0 {
|
if let strongSelf = self, let (searchResult, searchState, searchLocation) = searchResult {
|
||||||
let controller = ChatSearchResultsController(context: strongSelf.context, searchQuery: searchData.query, messages: searchResult.messages, navigateToMessageIndex: { index in
|
|
||||||
|
let controller = ChatSearchResultsController(context: strongSelf.context, location: searchLocation, searchQuery: searchData.query, searchResult: searchResult, searchState: searchState, navigateToMessageIndex: { index in
|
||||||
strongSelf.interfaceInteraction?.navigateMessageSearch(.index(index))
|
strongSelf.interfaceInteraction?.navigateMessageSearch(.index(index))
|
||||||
|
}, resultsUpdated: { results, state in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let updatedValue: (SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)? = (results, state, searchLocation)
|
||||||
|
strongSelf.searchResult.set(.single(updatedValue))
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||||
|
if let data = current.search {
|
||||||
|
let messageIndices = results.messages.map({ $0.index }).sorted()
|
||||||
|
var currentIndex = messageIndices.last
|
||||||
|
if let previousResultId = data.resultsState?.currentId {
|
||||||
|
for index in messageIndices {
|
||||||
|
if index.id >= previousResultId {
|
||||||
|
currentIndex = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, state: state, totalCount: results.totalCount, completed: results.completed)))
|
||||||
|
} else {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
if case let .inline(navigationController) = strongSelf.presentationInterfaceState.mode {
|
if case let .inline(navigationController) = strongSelf.presentationInterfaceState.mode {
|
||||||
@ -4958,15 +4982,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var items: [ActionSheetItem] = []
|
var items: [ActionSheetItem] = []
|
||||||
|
|
||||||
if self.presentationInterfaceState.isScheduledMessages {
|
if self.presentationInterfaceState.isScheduledMessages {
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
beginClear(.scheduledMessages)
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
|
||||||
|
beginClear(.scheduledMessages)
|
||||||
|
})
|
||||||
|
], parseMarkdown: true), in: .window(.root))
|
||||||
}))
|
}))
|
||||||
} else if canRemoveGlobally {
|
} else if canRemoveGlobally {
|
||||||
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
|
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
beginClear(.forEveryone)
|
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
|
||||||
|
beginClear(.forEveryone)
|
||||||
|
})
|
||||||
|
], parseMarkdown: true), in: .window(.root))
|
||||||
}))
|
}))
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
@ -4974,9 +5020,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
items.append(ActionSheetTextItem(title: text))
|
items.append(ActionSheetTextItem(title: text))
|
||||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
beginClear(.forLocalPeer)
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
|
||||||
|
beginClear(.forLocalPeer)
|
||||||
|
})
|
||||||
|
], parseMarkdown: true), in: .window(.root))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6094,7 +6151,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let search = searchMessages(account: self.context.account, location: searchState.location, query: searchState.query, state: nil, limit: limit)
|
let search = searchMessages(account: self.context.account, location: searchState.location, query: searchState.query, state: nil, limit: limit)
|
||||||
|> delay(0.2, queue: Queue.mainQueue())
|
|> delay(0.2, queue: Queue.mainQueue())
|
||||||
self.searchResult.set(search |> map(Optional.init))
|
self.searchResult.set(search
|
||||||
|
|> map { (result, state) -> (SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)? in
|
||||||
|
return (result, state, searchState.location)
|
||||||
|
})
|
||||||
|
|
||||||
searchDisposable.set((search
|
searchDisposable.set((search
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] results, updatedState in
|
|> deliverOnMainQueue).start(next: { [weak self] results, updatedState in
|
||||||
|
@ -105,7 +105,10 @@ private func chatListSearchContainerPreparedTransition(from fromEntries: [ChatLi
|
|||||||
class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let messages: [Message]
|
private let location: SearchMessagesLocation
|
||||||
|
private let searchQuery: String
|
||||||
|
private var searchResult: SearchMessagesResult
|
||||||
|
private var searchState: SearchMessagesState
|
||||||
|
|
||||||
private var interaction: ChatListNodeInteraction?
|
private var interaction: ChatListNodeInteraction?
|
||||||
|
|
||||||
@ -114,14 +117,23 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
|
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
var resultsUpdated: ((SearchMessagesResult, SearchMessagesState) -> Void)?
|
||||||
var resultSelected: ((Int) -> Void)?
|
var resultSelected: ((Int) -> Void)?
|
||||||
|
|
||||||
private let presentationDataPromise: Promise<ChatListPresentationData>
|
private let presentationDataPromise: Promise<ChatListPresentationData>
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, searchQuery: String, messages: [Message]) {
|
private var isLoadingMore = false
|
||||||
|
private let loadMoreDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private let previousEntries = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||||
|
|
||||||
|
init(context: AccountContext, location: SearchMessagesLocation, searchQuery: String, searchResult: SearchMessagesResult, searchState: SearchMessagesState) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.messages = messages
|
self.location = location
|
||||||
|
self.searchQuery = searchQuery
|
||||||
|
self.searchResult = searchResult
|
||||||
|
self.searchState = searchState
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations))
|
self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations))
|
||||||
|
|
||||||
@ -138,7 +150,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
|> map { presentationData -> [ChatListSearchEntry] in
|
|> map { presentationData -> [ChatListSearchEntry] in
|
||||||
var entries: [ChatListSearchEntry] = []
|
var entries: [ChatListSearchEntry] = []
|
||||||
|
|
||||||
for message in messages {
|
for message in searchResult.messages {
|
||||||
var peer = RenderedPeer(message: message)
|
var peer = RenderedPeer(message: message)
|
||||||
if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference {
|
if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference {
|
||||||
if let channelPeer = message.peers[migrationReference.peerId] {
|
if let channelPeer = message.peers[migrationReference.peerId] {
|
||||||
@ -156,8 +168,8 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
}, togglePeerSelected: { _ in
|
}, togglePeerSelected: { _ in
|
||||||
}, messageSelected: { [weak self] peer, message, _ in
|
}, messageSelected: { [weak self] peer, message, _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let index = strongSelf.messages.firstIndex(where: { $0.index == message.index }) {
|
if let index = strongSelf.searchResult.messages.firstIndex(where: { $0.index == message.index }) {
|
||||||
strongSelf.resultSelected?(strongSelf.messages.count - index - 1)
|
strongSelf.resultSelected?(strongSelf.searchResult.messages.count - index - 1)
|
||||||
}
|
}
|
||||||
strongSelf.listNode.clearHighlightAnimated(true)
|
strongSelf.listNode.clearHighlightAnimated(true)
|
||||||
}
|
}
|
||||||
@ -175,17 +187,86 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
interaction.searchTextHighightState = searchQuery
|
interaction.searchTextHighightState = searchQuery
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
let previousEntries = Atomic<[ChatListSearchEntry]?>(value: nil)
|
|
||||||
self.disposable.set((signal
|
self.disposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = strongSelf.previousEntries.swap(entries)
|
||||||
|
|
||||||
let firstTime = previousEntries == nil
|
let firstTime = previousEntries == nil
|
||||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction)
|
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction)
|
||||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case let .known(value) = offset, value < 100.0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.searchResult.completed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.isLoadingMore {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
self.loadMoreDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadMore() {
|
||||||
|
self.isLoadingMore = true
|
||||||
|
|
||||||
|
self.loadMoreDisposable.set((searchMessages(account: self.context.account, location: self.location, query: self.searchQuery, state: self.searchState)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] (updatedResult, updatedState) in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let interaction = strongSelf.interaction else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.isLoadingMore = false
|
||||||
|
strongSelf.searchResult = updatedResult
|
||||||
|
strongSelf.searchState = updatedState
|
||||||
|
strongSelf.resultsUpdated?(updatedResult, updatedState)
|
||||||
|
|
||||||
|
let context = strongSelf.context
|
||||||
|
|
||||||
|
let signal = strongSelf.presentationDataPromise.get()
|
||||||
|
|> map { presentationData -> [ChatListSearchEntry] in
|
||||||
|
var entries: [ChatListSearchEntry] = []
|
||||||
|
|
||||||
|
for message in updatedResult.messages {
|
||||||
|
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, nil, presentationData))
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.disposable.set((signal
|
||||||
|
|> deliverOnMainQueue).start(next: { entries in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let previousEntries = strongSelf.previousEntries.swap(entries)
|
||||||
|
|
||||||
|
let firstTime = previousEntries == nil
|
||||||
|
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction)
|
||||||
|
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user