From 4eb0863f3a06487ef9212141c8aa6598c406f40f Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 21 Jul 2026 12:47:04 +0300 Subject: [PATCH] Surface search set query errors with an error indicator --- .../FlowCoordinators/UserSessionFlowCoordinator.swift | 3 ++- .../Screens/SearchScreen/SearchScreenCoordinator.swift | 4 +++- .../Screens/SearchScreen/SearchScreenViewModel.swift | 9 +++++++-- .../Sources/Screens/SearchScreen/View/SearchScreen.swift | 7 ++++++- UnitTests/Sources/SearchScreenViewModelTests.swift | 3 ++- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 51189af1d..39c7e7ac9 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -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) diff --git a/ElementX/Sources/Screens/SearchScreen/SearchScreenCoordinator.swift b/ElementX/Sources/Screens/SearchScreen/SearchScreenCoordinator.swift index 12f5fc5d2..9ef9a26f0 100644 --- a/ElementX/Sources/Screens/SearchScreen/SearchScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SearchScreen/SearchScreenCoordinator.swift @@ -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() { diff --git a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift index f57806f71..99b96e4c7 100644 --- a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift @@ -14,6 +14,7 @@ typealias SearchScreenViewModelType = StateStoreViewModelV2? private var loadingObservationTask: Task? private var setQueryTask: Task? @@ -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)) + } } } } diff --git a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift index 8472bb6f4..88b6dca54 100644 --- a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift +++ b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift @@ -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) diff --git a/UnitTests/Sources/SearchScreenViewModelTests.swift b/UnitTests/Sources/SearchScreenViewModelTests.swift index 12cb08f89..a2e5c7a82 100644 --- a/UnitTests/Sources/SearchScreenViewModelTests.swift +++ b/UnitTests/Sources/SearchScreenViewModelTests.swift @@ -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