Surface search set query errors with an error indicator

This commit is contained in:
Stefan Ceriu
2026-07-21 12:47:04 +03:00
parent 32a54c5b71
commit 4eb0863f3a
5 changed files with 20 additions and 6 deletions
@@ -97,7 +97,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
if flowParameters.appSettings.globalSearchEnabled, #available(iOS 26.0, *) {
let searchCoordinator = SearchScreenCoordinator(parameters: .init(roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider,
clientProxy: flowParameters.userSession.clientProxy,
mediaProvider: flowParameters.userSession.mediaProvider))
mediaProvider: flowParameters.userSession.mediaProvider,
userIndicatorController: flowParameters.userIndicatorController))
let searchStackCoordinator = NavigationStackCoordinator()
searchStackCoordinator.setRootCoordinator(searchCoordinator)
@@ -12,6 +12,7 @@ struct SearchScreenCoordinatorParameters {
let roomSummaryProvider: RoomSummaryProviderProtocol
let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
enum SearchScreenCoordinatorAction {
@@ -32,7 +33,8 @@ final class SearchScreenCoordinator: CoordinatorProtocol {
init(parameters: SearchScreenCoordinatorParameters) {
viewModel = SearchScreenViewModel(roomSummaryProvider: parameters.roomSummaryProvider,
clientProxy: parameters.clientProxy,
mediaProvider: parameters.mediaProvider)
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)
}
func start() {
@@ -14,6 +14,7 @@ typealias SearchScreenViewModelType = StateStoreViewModelV2<SearchScreenViewStat
class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelProtocol {
private let roomSummaryProvider: RoomSummaryProviderProtocol
private let searchService: SearchServiceProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private var searchQueryObservationTask: Task<Void, Never>?
private var loadingObservationTask: Task<Void, Never>?
private var setQueryTask: Task<Void, Never>?
@@ -26,10 +27,12 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
init(roomSummaryProvider: RoomSummaryProviderProtocol,
clientProxy: ClientProxyProtocol,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
initialSearchQuery: String = "",
initialSearchMode: SearchScreenMode = .rooms) {
self.roomSummaryProvider = roomSummaryProvider
searchService = clientProxy.searchService
self.userIndicatorController = userIndicatorController
super.init(initialViewState: SearchScreenViewState(bindings: .init(searchQuery: initialSearchQuery, searchMode: initialSearchMode)),
mediaProvider: mediaProvider)
@@ -133,8 +136,10 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
} else {
roomSummaryProvider.setFilter(.search(query: searchQuery))
setQueryTask = Task { [weak self] in
// TODO: @stefanceriu Surface set query errors
_ = await self?.searchService.setQuery(searchQuery)
guard let self else { return }
if case .failure = await searchService.setQuery(searchQuery), !Task.isCancelled {
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
}
}
}
}
@@ -467,23 +467,28 @@ private final class KeyNavigatingSearchTextField: UISearchTextField {
struct SearchScreen_Previews: PreviewProvider, TestablePreview {
static let emptyViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))),
clientProxy: makeClientProxy(),
mediaProvider: MediaProviderMock(.init()))
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static let noResultsViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))),
clientProxy: makeClientProxy(),
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
initialSearchQuery: "John Doe")
static let roomsViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))),
clientProxy: makeClientProxy(),
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
initialSearchQuery: "Foundation")
static let messagesViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))),
clientProxy: makeClientProxy(searchService: makeSearchService(results: .mockResults)),
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
initialSearchQuery: "Foundation",
initialSearchMode: .messages)
static let loadingMessagesViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))),
clientProxy: makeClientProxy(searchService: makeSearchService(paginationState: .loading)),
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
initialSearchQuery: "Foundation",
initialSearchMode: .messages)
@@ -27,7 +27,8 @@ struct SearchScreenViewModelTests {
viewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))),
clientProxy: clientProxy,
mediaProvider: MediaProviderMock(.init()))
mediaProvider: MediaProviderMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
}
@Test