From b135e8108aeab20979f25442a8bb855d6f67e5d5 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:58:34 +0100 Subject: [PATCH] Export screen: persist (in-memory) filename during scan --- .../java/org/fairscan/app/MainActivity.kt | 1 + .../app/ui/screens/export/ExportScreen.kt | 43 +++++-------------- .../app/ui/screens/export/ExportUiState.kt | 1 + .../app/ui/screens/export/ExportViewModel.kt | 43 ++++++++++++++----- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 0a437f2..81876cf 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -192,6 +192,7 @@ class MainActivity : ComponentActivity() { open = { item -> openUri(item.uri, item.format.mimeType) }, ), onCloseScan = { + exportViewModel.resetFilename() viewModel.startNewDocument() viewModel.navigateTo(Screen.Main.Home) } 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 4842f85..7095cc0 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 @@ -64,7 +64,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -102,9 +101,6 @@ import org.fairscan.app.ui.state.DocumentUiModel import org.fairscan.app.ui.theme.FairScanTheme import java.io.File import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale @Composable fun ExportScreenWrapper( @@ -117,38 +113,27 @@ fun ExportScreenWrapper( BackHandler { navigation.back() } val showConfirmationDialog = rememberSaveable { mutableStateOf(false) } - val filename = remember { mutableStateOf(defaultFilename()) } + LaunchedEffect(Unit) { - pdfActions.setFilename(filename.value) pdfActions.initializeExportScreen() } val onFilenameChange = { newName:String -> - filename.value = newName pdfActions.setFilename(newName) } - val ensureCorrectFileName = { - val value = filename.value.trim().ifEmpty { defaultFilename() } - if (value != filename.value) { - onFilenameChange(value) - } - } ExportScreen( - filename = filename, onFilenameChange = onFilenameChange, uiState = uiState, currentDocument = currentDocument, navigation = navigation, onShare = { if (!uiState.isSaving) { - ensureCorrectFileName() pdfActions.share() } }, onSave = { if (!uiState.isSaving) { - ensureCorrectFileName() pdfActions.save() } }, @@ -171,7 +156,6 @@ fun ExportScreenWrapper( @OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( - filename: MutableState, onFilenameChange: (String) -> Unit, uiState: ExportUiState, currentDocument: DocumentUiModel, @@ -203,7 +187,7 @@ fun ExportScreen( ) { PdfInfosAndResultBar(uiState, currentDocument, onOpen, onThumbnailClick) Spacer(Modifier.weight(1f)) // push buttons down - MainActions(filename, onFilenameChange, uiState, onShare, onSave, onCloseScan) + MainActions(onFilenameChange, uiState, onShare, onSave, onCloseScan) } } else { Row( @@ -218,7 +202,7 @@ fun ExportScreen( PdfInfosAndResultBar(uiState, currentDocument, onOpen, onThumbnailClick) } Column(modifier = Modifier.weight(1f)) { - MainActions(filename, onFilenameChange, uiState, onShare, onSave, onCloseScan) + MainActions(onFilenameChange, uiState, onShare, onSave, onCloseScan) } } @@ -348,12 +332,12 @@ private fun SaveStatusBar( @Composable private fun FilenameTextField( - filename: MutableState, + filename: String, onFilenameChange: (String) -> Unit, ) { val focusRequester = remember { FocusRequester() } OutlinedTextField( - value = filename.value, + value = filename, onValueChange = onFilenameChange, label = { Text(stringResource(R.string.filename)) }, singleLine = true, @@ -361,9 +345,9 @@ private fun FilenameTextField( .fillMaxWidth() .focusRequester(focusRequester), trailingIcon = { - if (filename.value.isNotEmpty()) { + if (filename.isNotEmpty()) { IconButton(onClick = { - filename.value = "" + onFilenameChange("") focusRequester.requestFocus() }) { Icon(Icons.Default.Clear, stringResource(R.string.clear_text)) @@ -375,7 +359,6 @@ private fun FilenameTextField( @Composable private fun MainActions( - filename: MutableState, onFilenameChange: (String) -> Unit, uiState: ExportUiState, onShare: () -> Unit, @@ -386,7 +369,7 @@ private fun MainActions( verticalArrangement = Arrangement.spacedBy(12.dp) ) { ActionSurface { - FilenameTextField(filename, onFilenameChange) + FilenameTextField(uiState.filename, onFilenameChange) Row( horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -658,11 +641,6 @@ fun providerLabel(authority: String): String = authority } -fun defaultFilename(): String { - val timestamp = SimpleDateFormat("yyyy-MM-dd HH.mm.ss", Locale.getDefault()).format(Date()) - return "Scan $timestamp" -} - fun formatFileSize(sizeInBytes: Long?, context: Context): String { return if (sizeInBytes == null) context.getString(R.string.unknown_size) else Formatter.formatShortFileSize(context, sizeInBytes) @@ -672,7 +650,7 @@ fun formatFileSize(sizeInBytes: Long?, context: Context): String { @Composable fun PreviewExportScreenDuringGeneration() { ExportPreviewToCustomize( - uiState = ExportUiState(isGenerating = true) + uiState = ExportUiState(isGenerating = true, filename = "Scan 2025-12-15 07:00:51") ) } @@ -695,7 +673,7 @@ fun PreviewExportScreenAfterSave() { uiState = ExportUiState( result = ExportResult.Pdf(file, 442897L, 3), savedBundle = SavedBundle( - listOf(SavedItem(file.toUri(), defaultFilename() + ".pdf", PDF)) + listOf(SavedItem(file.toUri(), "12345.pdf", PDF)) ), ), ) @@ -728,7 +706,6 @@ fun PreviewExportScreenAfterSaveHorizontal() { fun ExportPreviewToCustomize(uiState: ExportUiState) { FairScanTheme { ExportScreen( - filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42") }, onFilenameChange = {_->}, uiState = uiState, currentDocument = fakeDocument( 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 89d3198..59c6e26 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 @@ -19,6 +19,7 @@ import org.fairscan.app.ui.screens.settings.ExportFormat data class ExportUiState( val format: ExportFormat = ExportFormat.PDF, + val filename: String = "", val isGenerating: Boolean = false, val isSaving: Boolean = false, val result: ExportResult? = null, 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 891d985..d0add1f 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 @@ -49,6 +49,9 @@ import org.fairscan.app.ui.screens.settings.ExportFormat import java.io.File import java.io.FileInputStream import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -83,15 +86,38 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit val uiState: StateFlow = _uiState.asStateFlow() private var preparationJob: Job? = null - private var desiredFilename: String = "" private var exportFormat = ExportFormat.PDF fun setFilename(name: String) { - desiredFilename = name + _uiState.update { + it.copy(filename = name) + } + } + + fun resetFilename() { + _uiState.update { + it.copy(filename = "") + } + } + + private fun defaultFilename(): String { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH.mm.ss", Locale.getDefault()).format(Date()) + return "Scan $timestamp" + } + + private fun ensureValidFilename() { + _uiState.update { + val normalized = it.filename.trim().ifEmpty { defaultFilename() } + if (normalized != it.filename) { + it.copy(filename = normalized) + } else it + } } fun initializeExportScreen() { - cancelPreparation() + preparationJob?.cancel() + _uiState.update { ExportUiState(filename = it.filename) } + ensureValidFilename() preparationJob = viewModelScope.launch { val exportQuality = settingsRepository.exportQuality.first() @@ -138,20 +164,17 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit ExportResult.Jpeg(files, sizeInBytes) } - fun cancelPreparation() { - preparationJob?.cancel() - _uiState.value = ExportUiState() - } - fun setAsShared() { _uiState.update { it.copy(hasShared = true) } } fun applyRenaming(): ExportResult? { val result = _uiState.value.result ?: return null + ensureValidFilename() + val filename = _uiState.value.filename when (result) { is ExportResult.Pdf -> { - val fileName = FileManager.addPdfExtensionIfMissing(desiredFilename) + val fileName = FileManager.addPdfExtensionIfMissing(filename) val newFile = File(result.file.parentFile, fileName) val tempFile = result.file if (tempFile.absolutePath != newFile.absolutePath) { @@ -166,7 +189,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit } } is ExportResult.Jpeg -> { - val base = desiredFilename.removeSuffix(".jpg") + val base = filename.removeSuffix(".jpg") val files = result.files val renamedFiles = files.mapIndexed { index, file -> val indexSuffix = if (files.size == 1) "" else "_${index + 1}"