Export screen: add a spinner during the save operation

This commit is contained in:
Pierre-Yves Nicolas
2026-01-29 21:07:18 +01:00
parent 85c966a7aa
commit f32b4f8799
3 changed files with 41 additions and 13 deletions

View File

@@ -56,6 +56,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -117,19 +118,25 @@ fun ExportScreenWrapper(
uiState = uiState, uiState = uiState,
navigation = navigation, navigation = navigation,
onShare = { onShare = {
if (!uiState.isSaving) {
ensureCorrectFileName() ensureCorrectFileName()
pdfActions.share() pdfActions.share()
}
}, },
onSave = { onSave = {
if (!uiState.isSaving) {
ensureCorrectFileName() ensureCorrectFileName()
pdfActions.save() pdfActions.save()
}
}, },
onOpen = pdfActions.open, onOpen = pdfActions.open,
onCloseScan = { onCloseScan = {
if (!uiState.isSaving) {
if (uiState.hasSavedOrShared) if (uiState.hasSavedOrShared)
onCloseScan() onCloseScan()
else else
showConfirmationDialog.value = true showConfirmationDialog.value = true
}
}, },
) )
@@ -235,15 +242,32 @@ private fun TextFieldAndPdfInfos(
) )
} }
} }
SaveStatusBar(uiState, onOpen)
if (uiState.savedBundle != null) {
SaveInfoBar(uiState.savedBundle, onOpen)
}
if (uiState.errorMessage != null) { if (uiState.errorMessage != null) {
ErrorBar(uiState.errorMessage) 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 @Composable
private fun FilenameTextField( private fun FilenameTextField(
filename: MutableState<String>, filename: MutableState<String>,
@@ -299,7 +323,7 @@ private fun MainActions(
isPrimary = !uiState.hasSavedOrShared, isPrimary = !uiState.hasSavedOrShared,
icon = Icons.Default.Download, icon = Icons.Default.Download,
text = stringResource(R.string.save), text = stringResource(R.string.save),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f).alpha(if (uiState.isSaving) 0.6f else 1f)
) )
} }
ExportButton( ExportButton(

View File

@@ -20,6 +20,7 @@ import org.fairscan.app.ui.screens.settings.ExportFormat
data class ExportUiState( data class ExportUiState(
val format: ExportFormat = ExportFormat.PDF, val format: ExportFormat = ExportFormat.PDF,
val isGenerating: Boolean = false, val isGenerating: Boolean = false,
val isSaving: Boolean = false,
val result: ExportResult? = null, val result: ExportResult? = null,
val savedBundle: SavedBundle? = null, val savedBundle: SavedBundle? = null,
val hasShared: Boolean = false, val hasShared: Boolean = false,

View File

@@ -193,6 +193,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
fun onRequestSave(context: Context) { fun onRequestSave(context: Context) {
viewModelScope.launch { viewModelScope.launch {
_uiState.update {it.copy(isSaving = true, errorMessage = null, savedBundle = null) }
try { try {
// Must not run on the main thread: some SAF providers (e.g. Nextcloud) // Must not run on the main thread: some SAF providers (e.g. Nextcloud)
// may perform network I/O // may perform network I/O
@@ -208,6 +209,8 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
} catch (e: Exception) { } catch (e: Exception) {
logger.e("FairScan", "Failed to save PDF", e) logger.e("FairScan", "Failed to save PDF", e)
_events.emit(ExportEvent.SaveError) _events.emit(ExportEvent.SaveError)
} finally {
_uiState.update { it.copy(isSaving = false) }
} }
} }
} }