From f32b4f8799f86807bd9e7d068f7188b521b8a706 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Thu, 29 Jan 2026 21:07:18 +0100 Subject: [PATCH] Export screen: add a spinner during the save operation --- .../app/ui/screens/export/ExportScreen.kt | 50 ++++++++++++++----- .../app/ui/screens/export/ExportUiState.kt | 1 + .../app/ui/screens/export/ExportViewModel.kt | 3 ++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt index b682d0e..11d0c37 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportScreen.kt @@ -56,6 +56,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color @@ -117,19 +118,25 @@ fun ExportScreenWrapper( uiState = uiState, navigation = navigation, onShare = { - ensureCorrectFileName() - pdfActions.share() + if (!uiState.isSaving) { + ensureCorrectFileName() + pdfActions.share() + } }, onSave = { - ensureCorrectFileName() - pdfActions.save() + if (!uiState.isSaving) { + ensureCorrectFileName() + pdfActions.save() + } }, onOpen = pdfActions.open, onCloseScan = { - if (uiState.hasSavedOrShared) - onCloseScan() - else - showConfirmationDialog.value = true + if (!uiState.isSaving) { + if (uiState.hasSavedOrShared) + onCloseScan() + else + showConfirmationDialog.value = true + } }, ) @@ -235,15 +242,32 @@ private fun TextFieldAndPdfInfos( ) } } - - if (uiState.savedBundle != null) { - SaveInfoBar(uiState.savedBundle, onOpen) - } + SaveStatusBar(uiState, onOpen) if (uiState.errorMessage != null) { ErrorBar(uiState.errorMessage) } } +@Composable +private fun SaveStatusBar( + uiState: ExportUiState, + onOpen: (SavedItem) -> Unit, +) { + when { + uiState.isSaving -> { + Row(verticalAlignment = Alignment.CenterVertically) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp + ) + } + } + uiState.savedBundle != null -> { + SaveInfoBar(uiState.savedBundle, onOpen) + } + } +} + @Composable private fun FilenameTextField( filename: MutableState, @@ -299,7 +323,7 @@ private fun MainActions( isPrimary = !uiState.hasSavedOrShared, icon = Icons.Default.Download, text = stringResource(R.string.save), - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f).alpha(if (uiState.isSaving) 0.6f else 1f) ) } ExportButton( diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt index 9df11ce..636faa1 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportUiState.kt @@ -20,6 +20,7 @@ import org.fairscan.app.ui.screens.settings.ExportFormat data class ExportUiState( val format: ExportFormat = ExportFormat.PDF, val isGenerating: Boolean = false, + val isSaving: Boolean = false, val result: ExportResult? = null, val savedBundle: SavedBundle? = null, val hasShared: Boolean = false, diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt index 0f76270..501736f 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt @@ -193,6 +193,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit fun onRequestSave(context: Context) { viewModelScope.launch { + _uiState.update {it.copy(isSaving = true, errorMessage = null, savedBundle = null) } try { // Must not run on the main thread: some SAF providers (e.g. Nextcloud) // may perform network I/O @@ -208,6 +209,8 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit } catch (e: Exception) { logger.e("FairScan", "Failed to save PDF", e) _events.emit(ExportEvent.SaveError) + } finally { + _uiState.update { it.copy(isSaving = false) } } } }