Add a Messages tab to global search

Split global search into Rooms and Messages tabs. The Messages tab lists
matching events with the room's name and avatar, a sender-prefixed preview,
rich media (image/video/audio/file) previews, and a loading indicator, then
paginates as you scroll. Queries are debounced and the previous one is
superseded so late results can't clobber a newer search.
This commit is contained in:
Stefan Ceriu
2026-07-02 13:25:56 +03:00
parent 5851100f07
commit 4fbfc87551
13 changed files with 727 additions and 81 deletions
@@ -98,6 +98,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
if flowParameters.appSettings.globalSearchEnabled, #available(iOS 26.0, *) {
let searchCoordinator = SearchScreenCoordinator(parameters: .init(roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider,
searchService: flowParameters.userSession.clientProxy.searchService,
clientProxy: flowParameters.userSession.clientProxy,
mediaProvider: flowParameters.userSession.mediaProvider))
let searchStackCoordinator = NavigationStackCoordinator()
searchStackCoordinator.setRootCoordinator(searchCoordinator)
@@ -222,20 +224,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
}
// swiftlint:disable:next function_body_length
private func setupObservers() {
searchScreenCoordinator?.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentRoom(let roomID):
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
case .cancel:
// Return to the tab the user came from, but never back into search.
navigationTabCoordinator.selectedTab = navigationTabCoordinator.previousTab == .search ? .chats : navigationTabCoordinator.previousTab ?? .chats
}
}
.store(in: &cancellables)
chatsTabFlowCoordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
@@ -334,6 +324,23 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
}
.store(in: &cancellables)
searchScreenCoordinator?.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentRoom(let roomID, let eventID):
if let eventID {
handleAppRoute(.event(eventID: eventID, roomID: roomID, via: []), animated: true)
} else {
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
}
case .cancel:
// Return to the tab the user came from, but never back into search.
navigationTabCoordinator.selectedTab = navigationTabCoordinator.previousTab == .search ? .chats : navigationTabCoordinator.previousTab ?? .chats
}
}
.store(in: &cancellables)
}
// MARK: - Onboarding