diff --git a/TableProMobile/TableProMobile/ViewModels/DataBrowserViewModel.swift b/TableProMobile/TableProMobile/ViewModels/DataBrowserViewModel.swift index 5b70e4cb1..6425eb9f9 100644 --- a/TableProMobile/TableProMobile/ViewModels/DataBrowserViewModel.swift +++ b/TableProMobile/TableProMobile/ViewModels/DataBrowserViewModel.swift @@ -94,10 +94,15 @@ final class DataBrowserViewModel { underlying: nil ) isLoading = false + isPageLoading = false return } - if isInitial || legacyRows.isEmpty { isLoading = true } + if isInitial || legacyRows.isEmpty { + isLoading = true + } else { + isPageLoading = true + } loadError = nil do { @@ -119,6 +124,7 @@ final class DataBrowserViewModel { if case .error(let err) = phase { loadError = err isLoading = false + isPageLoading = false return } @@ -139,12 +145,14 @@ final class DataBrowserViewModel { } isLoading = false + isPageLoading = false } catch { loadError = ErrorClassifier.classify( error, context: ErrorContext(operation: "loadData", databaseType: databaseType, host: host) ) isLoading = false + isPageLoading = false } } @@ -246,9 +254,7 @@ final class DataBrowserViewModel { } private func navigatePage() async { - isPageLoading = true await load() - isPageLoading = false } // MARK: - Sort / Filter / Search diff --git a/TableProMobile/TableProMobile/Views/DataBrowserView.swift b/TableProMobile/TableProMobile/Views/DataBrowserView.swift index 688a199d3..aca043952 100644 --- a/TableProMobile/TableProMobile/Views/DataBrowserView.swift +++ b/TableProMobile/TableProMobile/Views/DataBrowserView.swift @@ -69,7 +69,7 @@ struct DataBrowserView: View { ] } .toolbar { topToolbar } - .toolbar(rows.isEmpty && !viewModel.hasActiveSearch && !viewModel.hasActiveFilters ? .hidden : .visible, for: .bottomBar) + .toolbar(rows.isEmpty && !viewModel.hasActiveSearch && !viewModel.hasActiveFilters && !viewModel.isPageLoading ? .hidden : .visible, for: .bottomBar) .toolbar { paginationToolbar } .task { viewModel.attach(session: session, table: table, databaseType: connection.type, host: connection.host) @@ -199,6 +199,9 @@ struct DataBrowserView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let appError = viewModel.loadError { ErrorView(error: appError) { await viewModel.load() } + } else if rows.isEmpty, viewModel.isPageLoading { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) } else if rows.isEmpty, viewModel.hasActiveSearch { ContentUnavailableView.search(text: viewModel.activeSearchText) } else if rows.isEmpty { diff --git a/TableProMobile/TableProMobileTests/DataBrowserViewModelTests.swift b/TableProMobile/TableProMobileTests/DataBrowserViewModelTests.swift index 4f5b9ed7e..30693718a 100644 --- a/TableProMobile/TableProMobileTests/DataBrowserViewModelTests.swift +++ b/TableProMobile/TableProMobileTests/DataBrowserViewModelTests.swift @@ -92,6 +92,32 @@ struct DataBrowserViewModelTests { #expect(vm.activeSearchText == "") } + @Test("clearSearch with existing rows replaces them without leaving loading flags stuck") + func clearSearchReplacesRowsCleanly() async { + let driver = MockDatabaseDriver() + driver.scriptedColumns = makeColumns() + driver.scriptedExecuteResults = [ + .success(QueryResult(columns: makeColumns(), rows: [["1", "Alice"]], rowsAffected: 0, executionTime: 0)), + .success(QueryResult(columns: [], rows: [["1"]], rowsAffected: 0, executionTime: 0)) + ] + let vm = DataBrowserViewModel() + vm.attach(session: makeSession(driver: driver), table: TableInfo(name: "users"), databaseType: .mysql, host: "localhost") + await vm.load(isInitial: true) + #expect(vm.legacyRows.count == 1) + #expect(vm.isLoading == false) + #expect(vm.isPageLoading == false) + + driver.scriptedExecuteResults = [ + .success(QueryResult(columns: makeColumns(), rows: [["1", "Alice"], ["2", "Bob"]], rowsAffected: 0, executionTime: 0)), + .success(QueryResult(columns: [], rows: [["2"]], rowsAffected: 0, executionTime: 0)) + ] + await vm.clearSearch() + + #expect(vm.isLoading == false) + #expect(vm.isPageLoading == false) + #expect(vm.legacyRows.count == 2) + } + @Test("pagination prev/next clamps at boundaries") func paginationClamps() async { let driver = MockDatabaseDriver()