Move Save & Share to a new Export screen

This commit is contained in:
Pierre-Yves Nicolas
2025-08-29 15:33:58 +02:00
parent 7b125b06ba
commit 76a9417259
12 changed files with 226 additions and 218 deletions

View File

@@ -41,6 +41,7 @@ import org.fairscan.app.ui.theme.MyScanTheme
import org.fairscan.app.view.AboutScreen
import org.fairscan.app.view.CameraScreen
import org.fairscan.app.view.DocumentScreen
import org.fairscan.app.view.ExportScreenWrapper
import org.fairscan.app.view.HomeScreen
import org.fairscan.app.view.LibrariesScreen
import org.opencv.android.OpenCVLoader
@@ -67,6 +68,7 @@ class MainActivity : ComponentActivity() {
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },
toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) },
back = { viewModel.navigateBack() }
@@ -97,21 +99,26 @@ class MainActivity : ComponentActivity() {
DocumentScreen (
document = document,
initialPage = screen.initialPage,
navigation = navigation,
onDeleteImage = { id -> viewModel.deletePage(id) },
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
)
}
is Screen.Main.Export -> {
ExportScreenWrapper(
navigation = navigation,
pdfActions = PdfGenerationActions(
startGeneration = viewModel::startPdfGeneration,
cancelGeneration = viewModel::cancelPdfGeneration,
setFilename = viewModel::setFilename,
uiStateFlow = viewModel.pdfUiState,
sharePdf = { sharePdf(viewModel.getFinalPdf()) },
sharePdf = { sharePdf(viewModel.getFinalPdf(), viewModel) },
savePdf = { savePdf(viewModel.getFinalPdf(), viewModel) },
openPdf = { openPdf(viewModel.pdfUiState.value.savedFileUri) }
),
onStartNew = {
onCloseScan = {
viewModel.startNewDocument()
viewModel.navigateTo(Screen.Main.Home) },
onDeleteImage = { id -> viewModel.deletePage(id) },
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
viewModel.navigateTo(Screen.Main.Home)
},
)
}
is Screen.Overlay.About -> {
@@ -125,9 +132,10 @@ class MainActivity : ComponentActivity() {
}
}
private fun sharePdf(generatedPdf: GeneratedPdf?) {
private fun sharePdf(generatedPdf: GeneratedPdf?, viewModel: MainViewModel) {
if (generatedPdf == null)
return
viewModel.setPdfAsShared()
val file = generatedPdf.file
val authority = "${applicationContext.packageName}.fileprovider"
val fileUri = FileProvider.getUriForFile(this, authority, file)

View File

@@ -268,11 +268,7 @@ class MainViewModel(
}
fun startPdfGeneration() {
val currentState = _pdfUiState.value
if (currentState.isGenerating || currentState.generatedPdf != null) return
_pdfUiState.update { it.copy(isGenerating = true, errorMessage = null) }
cancelPdfGeneration()
generationJob = viewModelScope.launch {
try {
val result = generatePdf()
@@ -299,6 +295,10 @@ class MainViewModel(
_pdfUiState.value = PdfGenerationUiState()
}
fun setPdfAsShared() {
_pdfUiState.update { it.copy(hasSharedPdf = true) }
}
fun getFinalPdf(): GeneratedPdf? {
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
val tempFile = tempPdf.file
@@ -374,7 +374,6 @@ data class GeneratedPdf(
// TODO Move somewhere else: ViewModel should not depend on that
data class PdfGenerationActions(
val startGeneration: () -> Unit,
val cancelGeneration: () -> Unit,
val setFilename: (String) -> Unit,
val uiStateFlow: StateFlow<PdfGenerationUiState>,// TODO is it ok to have that here?
val sharePdf: () -> Unit,

View File

@@ -19,6 +19,7 @@ sealed class Screen {
object Home : Main()
object Camera : Main()
data class Document(val initialPage: Int = 0) : Main()
object Export : Main()
}
sealed class Overlay : Screen() {
object About : Overlay()
@@ -30,6 +31,7 @@ data class Navigation(
val toHomeScreen: () -> Unit,
val toCameraScreen: () -> Unit,
val toDocumentScreen: () -> Unit,
val toExportScreen: () -> Unit,
val toAboutScreen: () -> Unit,
val toLibrariesScreen: () -> Unit,
val back: () -> Unit,
@@ -57,6 +59,7 @@ data class NavigationState private constructor(val stack: List<Screen>) {
is Screen.Main.Home -> this // Back handled by system
is Screen.Main.Camera -> copy(stack = listOf(Screen.Main.Home))
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Document()))
is Screen.Overlay -> copy(stack = stack.dropLast(1))
}
}

View File

@@ -24,8 +24,11 @@ data class PdfGenerationUiState(
val desiredFilename: String = "",
val savedFileUri: Uri? = null,
val saveDirectoryName: String? = null,
val errorMessage: String? = null
)
val hasSharedPdf: Boolean = false,
val errorMessage: String? = null,
) {
val hasSavedOrSharedPdf get() = savedFileUri != null || hasSharedPdf
}
data class RecentDocumentUiState(
val file: File,

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2025 Pierre-Yves Nicolas
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.fairscan.app.view
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import org.fairscan.app.R
@Composable
fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>, title: String) {
ConfirmationDialog(title, stringResource(R.string.new_document_warning), showDialog, onConfirm)
}
@Composable
fun ConfirmationDialog(
title: String,
message: String,
showDialog: MutableState<Boolean>,
onConfirm: () -> Unit,
) {
AlertDialog(
title = { Text(title) },
text = { Text(message) },
confirmButton = {
TextButton(onClick = {
showDialog.value = false
onConfirm()
}) {
Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold)
}
},
dismissButton = {
TextButton(onClick = { showDialog.value = false }) {
Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold)
}
},
onDismissRequest = { showDialog.value = false },
)
}

View File

@@ -32,18 +32,14 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.RotateLeft
import androidx.compose.material.icons.automirrored.filled.RotateRight
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.PictureAsPdf
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@@ -53,16 +49,12 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.MutableStateFlow
import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
import org.fairscan.app.Navigation
import org.fairscan.app.PdfGenerationActions
import org.fairscan.app.R
import org.fairscan.app.ui.PdfGenerationUiState
import org.fairscan.app.ui.theme.MyScanTheme
@OptIn(ExperimentalMaterial3Api::class)
@@ -71,15 +63,11 @@ fun DocumentScreen(
document: DocumentUiModel,
initialPage: Int,
navigation: Navigation,
pdfActions: PdfGenerationActions,
onStartNew: () -> Unit,
onDeleteImage: (String) -> Unit,
onRotateImage: (String, Boolean) -> Unit,
) {
// TODO Check how often images are loaded
val showNewDocDialog = rememberSaveable { mutableStateOf(false) }
val showDeletePageDialog = rememberSaveable { mutableStateOf(false) }
val showPdfDialog = rememberSaveable { mutableStateOf(false) }
val currentPageIndex = rememberSaveable { mutableIntStateOf(initialPage) }
if (currentPageIndex.intValue >= document.pageCount()) {
currentPageIndex.intValue = document.pageCount() - 1
@@ -105,7 +93,7 @@ fun DocumentScreen(
),
onBack = navigation.back,
bottomBar = {
BottomBar(showPdfDialog, showNewDocDialog)
BottomBar(navigation)
},
pageListButton = {
SecondaryActionButton(
@@ -121,9 +109,6 @@ fun DocumentScreen(
{ showDeletePageDialog.value = true },
onRotateImage,
modifier)
if (showNewDocDialog.value) {
NewDocumentDialog(onConfirm = onStartNew, showNewDocDialog, stringResource(R.string.close_document))
}
if (showDeletePageDialog.value) {
ConfirmationDialog(
title = stringResource(R.string.delete_page),
@@ -131,12 +116,6 @@ fun DocumentScreen(
showDialog = showDeletePageDialog
) { onDeleteImage(document.pageId(currentPageIndex.intValue)) }
}
if (showPdfDialog.value) {
PdfGenerationBottomSheetWrapper(
onDismiss = { showPdfDialog.value = false },
pdfActions = pdfActions,
)
}
}
}
@@ -226,8 +205,7 @@ fun RotationButtons(
@Composable
private fun BottomBar(
showPdfDialog: MutableState<Boolean>,
showNewDocDialog: MutableState<Boolean>,
navigation: Navigation,
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -235,52 +213,13 @@ private fun BottomBar(
horizontalArrangement = Arrangement.End
) {
MainActionButton(
onClick = { showPdfDialog.value = true },
onClick = navigation.toExportScreen,
icon = Icons.Default.PictureAsPdf,
text = stringResource(R.string.export_pdf),
)
Spacer(modifier = Modifier.width(8.dp))
SecondaryActionButton(
icon = Icons.Default.Close,
contentDescription = stringResource(R.string.close_document),
onClick = { showNewDocDialog.value = true },
modifier = Modifier.padding(vertical = 8.dp)
)
}
}
@Composable
fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>, title: String) {
ConfirmationDialog(title, stringResource(R.string.new_document_warning), showDialog, onConfirm)
}
@Composable
private fun ConfirmationDialog(
title: String,
message: String,
showDialog: MutableState<Boolean>,
onConfirm: () -> Unit,
) {
AlertDialog(
title = { Text(title) },
text = { Text(message) },
confirmButton = {
TextButton(onClick = {
showDialog.value = false
onConfirm()
}) {
Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold)
}
},
dismissButton = {
TextButton(onClick = { showDialog.value = false }) {
Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold)
}
},
onDismissRequest = { showDialog.value = false },
)
}
@Composable
@Preview
fun DocumentScreenPreview() {
@@ -291,11 +230,6 @@ fun DocumentScreenPreview() {
LocalContext.current),
initialPage = 1,
navigation = dummyNavigation(),
pdfActions = PdfGenerationActions(
{}, {}, {},
MutableStateFlow(PdfGenerationUiState()),
{}, {}, {}),
onStartNew = {},
onDeleteImage = { _ -> },
onRotateImage = { _,_ -> },
)

View File

@@ -16,33 +16,31 @@ package org.fairscan.app.view
import android.content.Context
import android.text.format.Formatter
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.PictureAsPdf
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
@@ -50,16 +48,19 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import org.fairscan.app.GeneratedPdf
import org.fairscan.app.Navigation
import org.fairscan.app.PdfGenerationActions
import org.fairscan.app.R
import org.fairscan.app.ui.PdfGenerationUiState
@@ -69,13 +70,15 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PdfGenerationBottomSheetWrapper(
onDismiss: () -> Unit,
fun ExportScreenWrapper(
navigation: Navigation,
pdfActions: PdfGenerationActions,
modifier: Modifier = Modifier,
onCloseScan: () -> Unit,
) {
BackHandler { navigation.back() }
val showConfirmationDialog = rememberSaveable { mutableStateOf(false) }
val filename = remember { mutableStateOf(defaultFilename()) }
val uiState by pdfActions.uiStateFlow.collectAsState()
LaunchedEffect(Unit) {
@@ -83,11 +86,6 @@ fun PdfGenerationBottomSheetWrapper(
pdfActions.startGeneration()
}
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
LaunchedEffect(Unit) {
sheetState.expand()
}
val onFilenameChange = { newName:String ->
filename.value = newName
pdfActions.setFilename(newName)
@@ -99,19 +97,11 @@ fun PdfGenerationBottomSheetWrapper(
}
}
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
modifier = modifier.navigationBarsPadding()
) {
PdfGenerationBottomSheet(
ExportScreen(
filename = filename,
onFilenameChange = onFilenameChange,
uiState = uiState,
onDismiss = {
pdfActions.cancelGeneration()
onDismiss()
},
navigation = navigation,
onShare = {
ensureCorrectFileName()
pdfActions.sharePdf()
@@ -121,81 +111,88 @@ fun PdfGenerationBottomSheetWrapper(
pdfActions.savePdf()
},
onOpen = { pdfActions.openPdf() },
onCloseScan = {
if (uiState.hasSavedOrSharedPdf)
onCloseScan()
else
showConfirmationDialog.value = true
},
)
if (showConfirmationDialog.value) {
NewDocumentDialog(onCloseScan, showConfirmationDialog, stringResource(R.string.end_scan))
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PdfGenerationBottomSheet(
fun ExportScreen(
filename: MutableState<String>,
onFilenameChange: (String) -> Unit,
uiState: PdfGenerationUiState,
onDismiss: () -> Unit,
navigation: Navigation,
onShare: () -> Unit,
onSave: () -> Unit,
onOpen: () -> Unit,
onCloseScan: () -> Unit,
) {
Column() {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.export_pdf)) },
navigationIcon = { BackButton(navigation.back) },
actions = {
AboutScreenNavButton(onClick = navigation.toAboutScreen)
}
)
}
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 0.dp, start = 16.dp, end = 16.dp, bottom = 16.dp)
.padding(innerPadding)
.padding(16.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Row (verticalAlignment = Alignment.CenterVertically) {
Row {
Icon(
Icons.Default.PictureAsPdf, contentDescription = "PDF",
modifier = Modifier
.size(34.dp)
.padding(end = 8.dp)
)
Text(
stringResource(R.string.export_pdf),
style = MaterialTheme.typography.headlineSmall
)
}
CloseButton(onDismiss)
}
Spacer(Modifier.height(16.dp))
val focusRequester = remember { FocusRequester() }
OutlinedTextField(
value = filename.value,
onValueChange = onFilenameChange,
label = { Text(stringResource(R.string.filename)) },
singleLine = true,
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
trailingIcon = {
if (filename.value.isNotEmpty()) {
IconButton(onClick = {
filename.value = ""
focusRequester.requestFocus()
}) {
Icon(Icons.Default.Clear, contentDescription = "Effacer")
Icon(Icons.Default.Clear, stringResource(R.string.clear_text))
}
}
},
)
Spacer(Modifier.height(8.dp))
val pdf = uiState.generatedPdf
// PDF infos
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
if (uiState.isGenerating) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
Text(stringResource(R.string.creating_pdf), fontStyle = FontStyle.Italic)
} else if (pdf != null) {
val context = LocalContext.current
val formattedFileSize = formatFileSize(pdf.sizeInBytes, context)
Text(text = pageCountText(pdf.pageCount))
Text(
text = "${pageCountText(pdf.pageCount)} · $formattedFileSize",
style = MaterialTheme.typography.bodyMedium,
text = stringResource(R.string.file_size, formattedFileSize),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
Spacer(Modifier.height(24.dp))
MainActions(pdf, onShare, onSave)
}
if (uiState.saveDirectoryName != null) {
@@ -204,6 +201,25 @@ fun PdfGenerationBottomSheet(
if (uiState.errorMessage != null) {
ErrorBar(uiState.errorMessage)
}
Spacer(Modifier.weight(1f)) // push buttons down
// Export actions
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
MainActions(pdf, onShare, onSave)
OutlinedButton(
onClick = onCloseScan,
modifier = Modifier.fillMaxWidth(),
) {
Icon(Icons.Default.Done, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text(stringResource(R.string.end_scan))
}
}
}
}
}
@@ -271,21 +287,6 @@ private fun ErrorBar(errorMessage: String) {
)
}
@Composable
private fun CloseButton(onDismiss: () -> Unit) {
Box(Modifier.fillMaxWidth()) {
IconButton(
onClick = onDismiss,
modifier = Modifier.align(Alignment.TopEnd)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.close)
)
}
}
}
fun defaultFilename(): String {
val timestamp = SimpleDateFormat("yyyy-MM-dd HH.mm.ss", Locale.getDefault()).format(Date())
return "Scan $timestamp"
@@ -296,57 +297,48 @@ fun formatFileSize(sizeInBytes: Long?, context: Context): String {
else Formatter.formatShortFileSize(context, sizeInBytes)
}
@Preview(showBackground = true)
@Preview
@Composable
fun PreviewPdfGenerationDialogDuringGeneration() {
PreviewToCustomize(
fun PreviewExportScreenDuringGeneration() {
ExportPreviewToCustomize(
uiState = PdfGenerationUiState(isGenerating = true)
)
}
@Preview(showBackground = true)
@Preview
@Composable
fun PreviewPdfGenerationDialogAfterGeneration() {
PreviewToCustomize(
uiState = PdfGenerationUiState(
generatedPdf = GeneratedPdf(File("fake.pdf"), 442897L, 1)
)
)
}
@Preview(showBackground = true)
@Composable
fun PreviewPdfGenerationDialogAfterSave() {
fun PreviewExportScreenAfterSave() {
val file = File("fake.pdf")
PreviewToCustomize(
ExportPreviewToCustomize(
uiState = PdfGenerationUiState(
generatedPdf = GeneratedPdf(file, 442897L, 3),
savedFileUri = file.toUri()
)
savedFileUri = file.toUri(),
saveDirectoryName = "Downloads",
),
)
}
@Preview(showBackground = true)
@Preview
@Composable
fun PreviewPdfGenerationDialogWithError() {
PreviewToCustomize(
uiState = PdfGenerationUiState(
errorMessage = "PDF generation failed"
)
fun ExportScreenPreviewWithError() {
ExportPreviewToCustomize(
PdfGenerationUiState(errorMessage = "PDF generation failed")
)
}
@Composable
fun PreviewToCustomize(uiState: PdfGenerationUiState) {
fun ExportPreviewToCustomize(uiState: PdfGenerationUiState) {
MyScanTheme {
PdfGenerationBottomSheet(
filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42.pdf") },
ExportScreen(
filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42") },
onFilenameChange = {_->},
navigation = dummyNavigation(),
uiState = uiState,
onFilenameChange = {},
onDismiss = {},
onShare = {},
onSave = {},
onOpen = {},
onCloseScan = {},
)
}
}

View File

@@ -19,7 +19,7 @@ import android.graphics.BitmapFactory
import org.fairscan.app.Navigation
fun dummyNavigation(): Navigation {
return Navigation({}, {}, {}, {}, {}, {})
return Navigation({}, {}, {}, {}, {}, {}, {})
}
fun fakeDocument(pageIds: List<String>, context: Context): DocumentUiModel {

View File

@@ -10,16 +10,19 @@
<string name="clear_text">Text löschen</string>
<string name="close">Schließen</string>
<string name="close_document">Dokument schließen</string>
<string name="creating_pdf">PDF wird erstellt…</string>
<string name="current_document">Aktuelles Dokument</string>
<string name="delete_page">Seite löschen</string>
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
<string name="document">Dokument</string>
<string name="end_scan">Scan beenden</string>
<string name="error">Fehler: %1$s</string>
<string name="error_no_document">Kein Dokument erkannt</string>
<string name="error_no_pdf_app">Keine App zum Öffnen von PDF gefunden</string>
<string name="error_save">PDF konnte nicht gespeichert werden</string>
<string name="export_pdf">PDF exportieren</string>
<string name="filename">Dateiname</string>
<string name="file_size">Dateigröße: %1$s</string>
<string name="grant_permission">Berechtigung erteilen</string>
<string name="last_saved_documents">Zuletzt gespeicherte Dokumente</string>
<string name="libraries">Bibliotheken</string>
@@ -28,7 +31,7 @@
<string name="license">Lizenz</string>
<string name="licensed_under">Diese Anwendung ist unter der GNU General Public License v3.0 lizenziert.</string>
<string name="new_document">Neues Dokument</string>
<string name="new_document_warning">Das aktuelle Dokument geht verloren, wenn Sie es nicht gespeichert haben. Möchten Sie fortfahren?</string>
<string name="new_document_warning">Das aktuelle Dokument geht verloren. Möchten Sie fortfahren?</string>
<string name="open">Öffnen</string>
<string name="open_pdf">PDF öffnen</string>
<string name="pdf_saved_to">PDF gespeichert unter %1$s</string>

View File

@@ -9,16 +9,19 @@
<string name="clear_text">Effacer le text</string>
<string name="close">Fermer</string>
<string name="close_document">Fermer le document</string>
<string name="creating_pdf">Création du PDF…</string>
<string name="current_document">Document en cours</string>
<string name="delete_page">Supprimer la page</string>
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
<string name="document">Document</string>
<string name="end_scan">Terminer le scan</string>
<string name="error">Erreur : %1$s</string>
<string name="error_no_document">Aucun document détecté</string>
<string name="error_no_pdf_app">Aucune application trouvée pour ouvrir un PDF</string>
<string name="error_save">Échec de l\'enregistrement du PDF</string>
<string name="export_pdf">Exporter en PDF</string>
<string name="filename">Nom de fichier</string>
<string name="file_size">Taille du fichier : %1$s</string>
<string name="grant_permission">Autoriser</string>
<string name="last_saved_documents">Derniers documents enregistrés</string>
<string name="libraries">Bibliothèques</string>
@@ -27,7 +30,7 @@
<string name="license">Licence</string>
<string name="licensed_under">Cette application est distribuée sous licence GNU General Public License v3.0.</string>
<string name="new_document">Nouveau document</string>
<string name="new_document_warning">Le document en cours sera perdu si vous ne l\'avez pas enregistré. Voulez-vous continuer ?</string>
<string name="new_document_warning">Le scan en cours sera perdu. Voulez-vous continuer ?</string>
<string name="open">Ouvrir</string>
<string name="open_pdf">Ouvrir le PDF</string>
<string name="pdf_saved_to">PDF enregistré dans %1$s</string>

View File

@@ -10,16 +10,19 @@
<string name="clear_text">Clear text</string>
<string name="close">Close</string>
<string name="close_document">Close document</string>
<string name="creating_pdf">Creating PDF…</string>
<string name="current_document">Current document</string>
<string name="delete_page">Delete page</string>
<string name="delete_page_warning">Do you want to delete this page?</string>
<string name="document">Document</string>
<string name="end_scan">End scan</string>
<string name="error">Error: %1$s</string>
<string name="error_no_document">No document detected</string>
<string name="error_no_pdf_app">No app found to open PDF</string>
<string name="error_save">Failed to save PDF</string>
<string name="export_pdf">Export PDF</string>
<string name="filename">Filename</string>
<string name="file_size">File size: %1$s</string>
<string name="grant_permission">Grant permission</string>
<string name="last_saved_documents">Last saved documents</string>
<string name="libraries">Libraries</string>
@@ -28,7 +31,7 @@
<string name="license">License</string>
<string name="licensed_under">This application is licensed under the GNU General Public License v3.0.</string>
<string name="new_document">New document</string>
<string name="new_document_warning">The current document will be lost if you haven\'t saved it. Do you want to continue?</string>
<string name="new_document_warning">The current scan will be lost. Do you want to continue?</string>
<string name="open">Open</string>
<string name="open_pdf">Open PDF</string>
<string name="pdf_saved_to">PDF saved to %1$s</string>

View File

@@ -17,6 +17,7 @@ package org.fairscan.app
import org.assertj.core.api.Assertions.assertThat
import org.fairscan.app.Screen.Main.Camera
import org.fairscan.app.Screen.Main.Document
import org.fairscan.app.Screen.Main.Export
import org.fairscan.app.Screen.Main.Home
import org.fairscan.app.Screen.Overlay.About
import org.fairscan.app.Screen.Overlay.Libraries
@@ -36,10 +37,12 @@ class NavigationTest {
val atHome = NavigationState.initial()
val atCamera = atHome.navigateTo(Camera)
val atDocument = atHome.navigateTo(Document())
val atExport = atHome.navigateTo(Export)
assertThat(atHome.current).isEqualTo(Home)
assertThat(atCamera.current).isEqualTo(Camera)
assertThat(atDocument.current).isEqualTo(Document())
assertThat(atExport.current).isEqualTo(Export)
assertThat(atCamera.navigateTo(Document())).isEqualTo(atDocument)
assertThat(atDocument.navigateTo(Home)).isEqualTo(atHome)
@@ -48,6 +51,7 @@ class NavigationTest {
assertThat(atHome.navigateBack()).isEqualTo(atHome)
assertThat(atCamera.navigateBack()).isEqualTo(atHome)
assertThat(atDocument.navigateBack()).isEqualTo(atCamera)
assertThat(atExport.navigateBack()).isEqualTo(atDocument)
}
@Test