Move Save & Share to a new Export screen
This commit is contained in:
@@ -41,6 +41,7 @@ import org.fairscan.app.ui.theme.MyScanTheme
|
|||||||
import org.fairscan.app.view.AboutScreen
|
import org.fairscan.app.view.AboutScreen
|
||||||
import org.fairscan.app.view.CameraScreen
|
import org.fairscan.app.view.CameraScreen
|
||||||
import org.fairscan.app.view.DocumentScreen
|
import org.fairscan.app.view.DocumentScreen
|
||||||
|
import org.fairscan.app.view.ExportScreenWrapper
|
||||||
import org.fairscan.app.view.HomeScreen
|
import org.fairscan.app.view.HomeScreen
|
||||||
import org.fairscan.app.view.LibrariesScreen
|
import org.fairscan.app.view.LibrariesScreen
|
||||||
import org.opencv.android.OpenCVLoader
|
import org.opencv.android.OpenCVLoader
|
||||||
@@ -67,6 +68,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
|
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
|
||||||
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
|
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
|
||||||
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
|
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
|
||||||
|
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
|
||||||
toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },
|
toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },
|
||||||
toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) },
|
toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) },
|
||||||
back = { viewModel.navigateBack() }
|
back = { viewModel.navigateBack() }
|
||||||
@@ -97,21 +99,26 @@ class MainActivity : ComponentActivity() {
|
|||||||
DocumentScreen (
|
DocumentScreen (
|
||||||
document = document,
|
document = document,
|
||||||
initialPage = screen.initialPage,
|
initialPage = screen.initialPage,
|
||||||
|
navigation = navigation,
|
||||||
|
onDeleteImage = { id -> viewModel.deletePage(id) },
|
||||||
|
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Screen.Main.Export -> {
|
||||||
|
ExportScreenWrapper(
|
||||||
navigation = navigation,
|
navigation = navigation,
|
||||||
pdfActions = PdfGenerationActions(
|
pdfActions = PdfGenerationActions(
|
||||||
startGeneration = viewModel::startPdfGeneration,
|
startGeneration = viewModel::startPdfGeneration,
|
||||||
cancelGeneration = viewModel::cancelPdfGeneration,
|
|
||||||
setFilename = viewModel::setFilename,
|
setFilename = viewModel::setFilename,
|
||||||
uiStateFlow = viewModel.pdfUiState,
|
uiStateFlow = viewModel.pdfUiState,
|
||||||
sharePdf = { sharePdf(viewModel.getFinalPdf()) },
|
sharePdf = { sharePdf(viewModel.getFinalPdf(), viewModel) },
|
||||||
savePdf = { savePdf(viewModel.getFinalPdf(), viewModel) },
|
savePdf = { savePdf(viewModel.getFinalPdf(), viewModel) },
|
||||||
openPdf = { openPdf(viewModel.pdfUiState.value.savedFileUri) }
|
openPdf = { openPdf(viewModel.pdfUiState.value.savedFileUri) }
|
||||||
),
|
),
|
||||||
onStartNew = {
|
onCloseScan = {
|
||||||
viewModel.startNewDocument()
|
viewModel.startNewDocument()
|
||||||
viewModel.navigateTo(Screen.Main.Home) },
|
viewModel.navigateTo(Screen.Main.Home)
|
||||||
onDeleteImage = { id -> viewModel.deletePage(id) },
|
},
|
||||||
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Screen.Overlay.About -> {
|
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)
|
if (generatedPdf == null)
|
||||||
return
|
return
|
||||||
|
viewModel.setPdfAsShared()
|
||||||
val file = generatedPdf.file
|
val file = generatedPdf.file
|
||||||
val authority = "${applicationContext.packageName}.fileprovider"
|
val authority = "${applicationContext.packageName}.fileprovider"
|
||||||
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
val fileUri = FileProvider.getUriForFile(this, authority, file)
|
||||||
|
|||||||
@@ -268,11 +268,7 @@ class MainViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startPdfGeneration() {
|
fun startPdfGeneration() {
|
||||||
val currentState = _pdfUiState.value
|
cancelPdfGeneration()
|
||||||
if (currentState.isGenerating || currentState.generatedPdf != null) return
|
|
||||||
|
|
||||||
_pdfUiState.update { it.copy(isGenerating = true, errorMessage = null) }
|
|
||||||
|
|
||||||
generationJob = viewModelScope.launch {
|
generationJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = generatePdf()
|
val result = generatePdf()
|
||||||
@@ -299,6 +295,10 @@ class MainViewModel(
|
|||||||
_pdfUiState.value = PdfGenerationUiState()
|
_pdfUiState.value = PdfGenerationUiState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setPdfAsShared() {
|
||||||
|
_pdfUiState.update { it.copy(hasSharedPdf = true) }
|
||||||
|
}
|
||||||
|
|
||||||
fun getFinalPdf(): GeneratedPdf? {
|
fun getFinalPdf(): GeneratedPdf? {
|
||||||
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
|
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
|
||||||
val tempFile = tempPdf.file
|
val tempFile = tempPdf.file
|
||||||
@@ -374,7 +374,6 @@ data class GeneratedPdf(
|
|||||||
// TODO Move somewhere else: ViewModel should not depend on that
|
// TODO Move somewhere else: ViewModel should not depend on that
|
||||||
data class PdfGenerationActions(
|
data class PdfGenerationActions(
|
||||||
val startGeneration: () -> Unit,
|
val startGeneration: () -> Unit,
|
||||||
val cancelGeneration: () -> Unit,
|
|
||||||
val setFilename: (String) -> Unit,
|
val setFilename: (String) -> Unit,
|
||||||
val uiStateFlow: StateFlow<PdfGenerationUiState>,// TODO is it ok to have that here?
|
val uiStateFlow: StateFlow<PdfGenerationUiState>,// TODO is it ok to have that here?
|
||||||
val sharePdf: () -> Unit,
|
val sharePdf: () -> Unit,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ sealed class Screen {
|
|||||||
object Home : Main()
|
object Home : Main()
|
||||||
object Camera : Main()
|
object Camera : Main()
|
||||||
data class Document(val initialPage: Int = 0) : Main()
|
data class Document(val initialPage: Int = 0) : Main()
|
||||||
|
object Export : Main()
|
||||||
}
|
}
|
||||||
sealed class Overlay : Screen() {
|
sealed class Overlay : Screen() {
|
||||||
object About : Overlay()
|
object About : Overlay()
|
||||||
@@ -30,6 +31,7 @@ data class Navigation(
|
|||||||
val toHomeScreen: () -> Unit,
|
val toHomeScreen: () -> Unit,
|
||||||
val toCameraScreen: () -> Unit,
|
val toCameraScreen: () -> Unit,
|
||||||
val toDocumentScreen: () -> Unit,
|
val toDocumentScreen: () -> Unit,
|
||||||
|
val toExportScreen: () -> Unit,
|
||||||
val toAboutScreen: () -> Unit,
|
val toAboutScreen: () -> Unit,
|
||||||
val toLibrariesScreen: () -> Unit,
|
val toLibrariesScreen: () -> Unit,
|
||||||
val back: () -> 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.Home -> this // Back handled by system
|
||||||
is Screen.Main.Camera -> copy(stack = listOf(Screen.Main.Home))
|
is Screen.Main.Camera -> copy(stack = listOf(Screen.Main.Home))
|
||||||
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
|
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))
|
is Screen.Overlay -> copy(stack = stack.dropLast(1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ data class PdfGenerationUiState(
|
|||||||
val desiredFilename: String = "",
|
val desiredFilename: String = "",
|
||||||
val savedFileUri: Uri? = null,
|
val savedFileUri: Uri? = null,
|
||||||
val saveDirectoryName: String? = 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(
|
data class RecentDocumentUiState(
|
||||||
val file: File,
|
val file: File,
|
||||||
|
|||||||
56
app/src/main/java/org/fairscan/app/view/Dialogs.kt
Normal file
56
app/src/main/java/org/fairscan/app/view/Dialogs.kt
Normal 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 },
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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.RotateLeft
|
||||||
import androidx.compose.material.icons.automirrored.filled.RotateRight
|
import androidx.compose.material.icons.automirrored.filled.RotateRight
|
||||||
import androidx.compose.material.icons.filled.Add
|
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.filled.PictureAsPdf
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableIntState
|
import androidx.compose.runtime.MutableIntState
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
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.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import net.engawapg.lib.zoomable.rememberZoomState
|
import net.engawapg.lib.zoomable.rememberZoomState
|
||||||
import net.engawapg.lib.zoomable.zoomable
|
import net.engawapg.lib.zoomable.zoomable
|
||||||
import org.fairscan.app.Navigation
|
import org.fairscan.app.Navigation
|
||||||
import org.fairscan.app.PdfGenerationActions
|
|
||||||
import org.fairscan.app.R
|
import org.fairscan.app.R
|
||||||
import org.fairscan.app.ui.PdfGenerationUiState
|
|
||||||
import org.fairscan.app.ui.theme.MyScanTheme
|
import org.fairscan.app.ui.theme.MyScanTheme
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -71,15 +63,11 @@ fun DocumentScreen(
|
|||||||
document: DocumentUiModel,
|
document: DocumentUiModel,
|
||||||
initialPage: Int,
|
initialPage: Int,
|
||||||
navigation: Navigation,
|
navigation: Navigation,
|
||||||
pdfActions: PdfGenerationActions,
|
|
||||||
onStartNew: () -> Unit,
|
|
||||||
onDeleteImage: (String) -> Unit,
|
onDeleteImage: (String) -> Unit,
|
||||||
onRotateImage: (String, Boolean) -> Unit,
|
onRotateImage: (String, Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
// TODO Check how often images are loaded
|
// TODO Check how often images are loaded
|
||||||
val showNewDocDialog = rememberSaveable { mutableStateOf(false) }
|
|
||||||
val showDeletePageDialog = rememberSaveable { mutableStateOf(false) }
|
val showDeletePageDialog = rememberSaveable { mutableStateOf(false) }
|
||||||
val showPdfDialog = rememberSaveable { mutableStateOf(false) }
|
|
||||||
val currentPageIndex = rememberSaveable { mutableIntStateOf(initialPage) }
|
val currentPageIndex = rememberSaveable { mutableIntStateOf(initialPage) }
|
||||||
if (currentPageIndex.intValue >= document.pageCount()) {
|
if (currentPageIndex.intValue >= document.pageCount()) {
|
||||||
currentPageIndex.intValue = document.pageCount() - 1
|
currentPageIndex.intValue = document.pageCount() - 1
|
||||||
@@ -105,7 +93,7 @@ fun DocumentScreen(
|
|||||||
),
|
),
|
||||||
onBack = navigation.back,
|
onBack = navigation.back,
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
BottomBar(showPdfDialog, showNewDocDialog)
|
BottomBar(navigation)
|
||||||
},
|
},
|
||||||
pageListButton = {
|
pageListButton = {
|
||||||
SecondaryActionButton(
|
SecondaryActionButton(
|
||||||
@@ -121,9 +109,6 @@ fun DocumentScreen(
|
|||||||
{ showDeletePageDialog.value = true },
|
{ showDeletePageDialog.value = true },
|
||||||
onRotateImage,
|
onRotateImage,
|
||||||
modifier)
|
modifier)
|
||||||
if (showNewDocDialog.value) {
|
|
||||||
NewDocumentDialog(onConfirm = onStartNew, showNewDocDialog, stringResource(R.string.close_document))
|
|
||||||
}
|
|
||||||
if (showDeletePageDialog.value) {
|
if (showDeletePageDialog.value) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
title = stringResource(R.string.delete_page),
|
title = stringResource(R.string.delete_page),
|
||||||
@@ -131,12 +116,6 @@ fun DocumentScreen(
|
|||||||
showDialog = showDeletePageDialog
|
showDialog = showDeletePageDialog
|
||||||
) { onDeleteImage(document.pageId(currentPageIndex.intValue)) }
|
) { onDeleteImage(document.pageId(currentPageIndex.intValue)) }
|
||||||
}
|
}
|
||||||
if (showPdfDialog.value) {
|
|
||||||
PdfGenerationBottomSheetWrapper(
|
|
||||||
onDismiss = { showPdfDialog.value = false },
|
|
||||||
pdfActions = pdfActions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +205,7 @@ fun RotationButtons(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomBar(
|
private fun BottomBar(
|
||||||
showPdfDialog: MutableState<Boolean>,
|
navigation: Navigation,
|
||||||
showNewDocDialog: MutableState<Boolean>,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -235,52 +213,13 @@ private fun BottomBar(
|
|||||||
horizontalArrangement = Arrangement.End
|
horizontalArrangement = Arrangement.End
|
||||||
) {
|
) {
|
||||||
MainActionButton(
|
MainActionButton(
|
||||||
onClick = { showPdfDialog.value = true },
|
onClick = navigation.toExportScreen,
|
||||||
icon = Icons.Default.PictureAsPdf,
|
icon = Icons.Default.PictureAsPdf,
|
||||||
text = stringResource(R.string.export_pdf),
|
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
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun DocumentScreenPreview() {
|
fun DocumentScreenPreview() {
|
||||||
@@ -291,11 +230,6 @@ fun DocumentScreenPreview() {
|
|||||||
LocalContext.current),
|
LocalContext.current),
|
||||||
initialPage = 1,
|
initialPage = 1,
|
||||||
navigation = dummyNavigation(),
|
navigation = dummyNavigation(),
|
||||||
pdfActions = PdfGenerationActions(
|
|
||||||
{}, {}, {},
|
|
||||||
MutableStateFlow(PdfGenerationUiState()),
|
|
||||||
{}, {}, {}),
|
|
||||||
onStartNew = {},
|
|
||||||
onDeleteImage = { _ -> },
|
onDeleteImage = { _ -> },
|
||||||
onRotateImage = { _,_ -> },
|
onRotateImage = { _,_ -> },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,33 +16,31 @@ package org.fairscan.app.view
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.filled.Clear
|
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.Download
|
||||||
import androidx.compose.material.icons.filled.PictureAsPdf
|
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@@ -50,16 +48,19 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
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.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import org.fairscan.app.GeneratedPdf
|
import org.fairscan.app.GeneratedPdf
|
||||||
|
import org.fairscan.app.Navigation
|
||||||
import org.fairscan.app.PdfGenerationActions
|
import org.fairscan.app.PdfGenerationActions
|
||||||
import org.fairscan.app.R
|
import org.fairscan.app.R
|
||||||
import org.fairscan.app.ui.PdfGenerationUiState
|
import org.fairscan.app.ui.PdfGenerationUiState
|
||||||
@@ -69,13 +70,15 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PdfGenerationBottomSheetWrapper(
|
fun ExportScreenWrapper(
|
||||||
onDismiss: () -> Unit,
|
navigation: Navigation,
|
||||||
pdfActions: PdfGenerationActions,
|
pdfActions: PdfGenerationActions,
|
||||||
modifier: Modifier = Modifier,
|
onCloseScan: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
BackHandler { navigation.back() }
|
||||||
|
|
||||||
|
val showConfirmationDialog = rememberSaveable { mutableStateOf(false) }
|
||||||
val filename = remember { mutableStateOf(defaultFilename()) }
|
val filename = remember { mutableStateOf(defaultFilename()) }
|
||||||
val uiState by pdfActions.uiStateFlow.collectAsState()
|
val uiState by pdfActions.uiStateFlow.collectAsState()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -83,11 +86,6 @@ fun PdfGenerationBottomSheetWrapper(
|
|||||||
pdfActions.startGeneration()
|
pdfActions.startGeneration()
|
||||||
}
|
}
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
sheetState.expand()
|
|
||||||
}
|
|
||||||
|
|
||||||
val onFilenameChange = { newName:String ->
|
val onFilenameChange = { newName:String ->
|
||||||
filename.value = newName
|
filename.value = newName
|
||||||
pdfActions.setFilename(newName)
|
pdfActions.setFilename(newName)
|
||||||
@@ -99,19 +97,11 @@ fun PdfGenerationBottomSheetWrapper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
ExportScreen(
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
sheetState = sheetState,
|
|
||||||
modifier = modifier.navigationBarsPadding()
|
|
||||||
) {
|
|
||||||
PdfGenerationBottomSheet(
|
|
||||||
filename = filename,
|
filename = filename,
|
||||||
onFilenameChange = onFilenameChange,
|
onFilenameChange = onFilenameChange,
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
onDismiss = {
|
navigation = navigation,
|
||||||
pdfActions.cancelGeneration()
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
onShare = {
|
onShare = {
|
||||||
ensureCorrectFileName()
|
ensureCorrectFileName()
|
||||||
pdfActions.sharePdf()
|
pdfActions.sharePdf()
|
||||||
@@ -121,81 +111,88 @@ fun PdfGenerationBottomSheetWrapper(
|
|||||||
pdfActions.savePdf()
|
pdfActions.savePdf()
|
||||||
},
|
},
|
||||||
onOpen = { pdfActions.openPdf() },
|
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
|
@Composable
|
||||||
fun PdfGenerationBottomSheet(
|
fun ExportScreen(
|
||||||
filename: MutableState<String>,
|
filename: MutableState<String>,
|
||||||
onFilenameChange: (String) -> Unit,
|
onFilenameChange: (String) -> Unit,
|
||||||
uiState: PdfGenerationUiState,
|
uiState: PdfGenerationUiState,
|
||||||
onDismiss: () -> Unit,
|
navigation: Navigation,
|
||||||
onShare: () -> Unit,
|
onShare: () -> Unit,
|
||||||
onSave: () -> Unit,
|
onSave: () -> Unit,
|
||||||
onOpen: () -> 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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.padding(innerPadding)
|
||||||
.padding(top = 0.dp, start = 16.dp, end = 16.dp, bottom = 16.dp)
|
.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() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = filename.value,
|
value = filename.value,
|
||||||
onValueChange = onFilenameChange,
|
onValueChange = onFilenameChange,
|
||||||
label = { Text(stringResource(R.string.filename)) },
|
label = { Text(stringResource(R.string.filename)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(focusRequester),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (filename.value.isNotEmpty()) {
|
if (filename.value.isNotEmpty()) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
filename.value = ""
|
filename.value = ""
|
||||||
focusRequester.requestFocus()
|
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
|
val pdf = uiState.generatedPdf
|
||||||
|
|
||||||
|
// PDF infos
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
if (uiState.isGenerating) {
|
if (uiState.isGenerating) {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
Text(stringResource(R.string.creating_pdf), fontStyle = FontStyle.Italic)
|
||||||
} else if (pdf != null) {
|
} else if (pdf != null) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val formattedFileSize = formatFileSize(pdf.sizeInBytes, context)
|
val formattedFileSize = formatFileSize(pdf.sizeInBytes, context)
|
||||||
|
Text(text = pageCountText(pdf.pageCount))
|
||||||
Text(
|
Text(
|
||||||
text = "${pageCountText(pdf.pageCount)} · $formattedFileSize",
|
text = stringResource(R.string.file_size, formattedFileSize),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(24.dp))
|
|
||||||
|
|
||||||
MainActions(pdf, onShare, onSave)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiState.saveDirectoryName != null) {
|
if (uiState.saveDirectoryName != null) {
|
||||||
@@ -204,6 +201,25 @@ fun PdfGenerationBottomSheet(
|
|||||||
if (uiState.errorMessage != null) {
|
if (uiState.errorMessage != null) {
|
||||||
ErrorBar(uiState.errorMessage)
|
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 {
|
fun defaultFilename(): String {
|
||||||
val timestamp = SimpleDateFormat("yyyy-MM-dd HH.mm.ss", Locale.getDefault()).format(Date())
|
val timestamp = SimpleDateFormat("yyyy-MM-dd HH.mm.ss", Locale.getDefault()).format(Date())
|
||||||
return "Scan $timestamp"
|
return "Scan $timestamp"
|
||||||
@@ -296,57 +297,48 @@ fun formatFileSize(sizeInBytes: Long?, context: Context): String {
|
|||||||
else Formatter.formatShortFileSize(context, sizeInBytes)
|
else Formatter.formatShortFileSize(context, sizeInBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewPdfGenerationDialogDuringGeneration() {
|
fun PreviewExportScreenDuringGeneration() {
|
||||||
PreviewToCustomize(
|
ExportPreviewToCustomize(
|
||||||
uiState = PdfGenerationUiState(isGenerating = true)
|
uiState = PdfGenerationUiState(isGenerating = true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewPdfGenerationDialogAfterGeneration() {
|
fun PreviewExportScreenAfterSave() {
|
||||||
PreviewToCustomize(
|
|
||||||
uiState = PdfGenerationUiState(
|
|
||||||
generatedPdf = GeneratedPdf(File("fake.pdf"), 442897L, 1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun PreviewPdfGenerationDialogAfterSave() {
|
|
||||||
val file = File("fake.pdf")
|
val file = File("fake.pdf")
|
||||||
PreviewToCustomize(
|
ExportPreviewToCustomize(
|
||||||
uiState = PdfGenerationUiState(
|
uiState = PdfGenerationUiState(
|
||||||
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
generatedPdf = GeneratedPdf(file, 442897L, 3),
|
||||||
savedFileUri = file.toUri()
|
savedFileUri = file.toUri(),
|
||||||
)
|
saveDirectoryName = "Downloads",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewPdfGenerationDialogWithError() {
|
fun ExportScreenPreviewWithError() {
|
||||||
PreviewToCustomize(
|
ExportPreviewToCustomize(
|
||||||
uiState = PdfGenerationUiState(
|
PdfGenerationUiState(errorMessage = "PDF generation failed")
|
||||||
errorMessage = "PDF generation failed"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewToCustomize(uiState: PdfGenerationUiState) {
|
fun ExportPreviewToCustomize(uiState: PdfGenerationUiState) {
|
||||||
MyScanTheme {
|
MyScanTheme {
|
||||||
PdfGenerationBottomSheet(
|
ExportScreen(
|
||||||
filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42.pdf") },
|
filename = remember { mutableStateOf("Scan 2025-07-02 17.40.42") },
|
||||||
|
onFilenameChange = {_->},
|
||||||
|
navigation = dummyNavigation(),
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
onFilenameChange = {},
|
|
||||||
onDismiss = {},
|
|
||||||
onShare = {},
|
onShare = {},
|
||||||
onSave = {},
|
onSave = {},
|
||||||
onOpen = {},
|
onOpen = {},
|
||||||
|
onCloseScan = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ import android.graphics.BitmapFactory
|
|||||||
import org.fairscan.app.Navigation
|
import org.fairscan.app.Navigation
|
||||||
|
|
||||||
fun dummyNavigation(): Navigation {
|
fun dummyNavigation(): Navigation {
|
||||||
return Navigation({}, {}, {}, {}, {}, {})
|
return Navigation({}, {}, {}, {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fakeDocument(pageIds: List<String>, context: Context): DocumentUiModel {
|
fun fakeDocument(pageIds: List<String>, context: Context): DocumentUiModel {
|
||||||
|
|||||||
@@ -10,16 +10,19 @@
|
|||||||
<string name="clear_text">Text löschen</string>
|
<string name="clear_text">Text löschen</string>
|
||||||
<string name="close">Schließen</string>
|
<string name="close">Schließen</string>
|
||||||
<string name="close_document">Dokument 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="current_document">Aktuelles Dokument</string>
|
||||||
<string name="delete_page">Seite löschen</string>
|
<string name="delete_page">Seite löschen</string>
|
||||||
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
|
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
|
||||||
<string name="document">Dokument</string>
|
<string name="document">Dokument</string>
|
||||||
|
<string name="end_scan">Scan beenden</string>
|
||||||
<string name="error">Fehler: %1$s</string>
|
<string name="error">Fehler: %1$s</string>
|
||||||
<string name="error_no_document">Kein Dokument erkannt</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_no_pdf_app">Keine App zum Öffnen von PDF gefunden</string>
|
||||||
<string name="error_save">PDF konnte nicht gespeichert werden</string>
|
<string name="error_save">PDF konnte nicht gespeichert werden</string>
|
||||||
<string name="export_pdf">PDF exportieren</string>
|
<string name="export_pdf">PDF exportieren</string>
|
||||||
<string name="filename">Dateiname</string>
|
<string name="filename">Dateiname</string>
|
||||||
|
<string name="file_size">Dateigröße: %1$s</string>
|
||||||
<string name="grant_permission">Berechtigung erteilen</string>
|
<string name="grant_permission">Berechtigung erteilen</string>
|
||||||
<string name="last_saved_documents">Zuletzt gespeicherte Dokumente</string>
|
<string name="last_saved_documents">Zuletzt gespeicherte Dokumente</string>
|
||||||
<string name="libraries">Bibliotheken</string>
|
<string name="libraries">Bibliotheken</string>
|
||||||
@@ -28,7 +31,7 @@
|
|||||||
<string name="license">Lizenz</string>
|
<string name="license">Lizenz</string>
|
||||||
<string name="licensed_under">Diese Anwendung ist unter der GNU General Public License v3.0 lizenziert.</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">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">Öffnen</string>
|
||||||
<string name="open_pdf">PDF öffnen</string>
|
<string name="open_pdf">PDF öffnen</string>
|
||||||
<string name="pdf_saved_to">PDF gespeichert unter %1$s</string>
|
<string name="pdf_saved_to">PDF gespeichert unter %1$s</string>
|
||||||
|
|||||||
@@ -9,16 +9,19 @@
|
|||||||
<string name="clear_text">Effacer le text</string>
|
<string name="clear_text">Effacer le text</string>
|
||||||
<string name="close">Fermer</string>
|
<string name="close">Fermer</string>
|
||||||
<string name="close_document">Fermer le document</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="current_document">Document en cours</string>
|
||||||
<string name="delete_page">Supprimer la page</string>
|
<string name="delete_page">Supprimer la page</string>
|
||||||
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
|
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
|
||||||
<string name="document">Document</string>
|
<string name="document">Document</string>
|
||||||
|
<string name="end_scan">Terminer le scan</string>
|
||||||
<string name="error">Erreur : %1$s</string>
|
<string name="error">Erreur : %1$s</string>
|
||||||
<string name="error_no_document">Aucun document détecté</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_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="error_save">Échec de l\'enregistrement du PDF</string>
|
||||||
<string name="export_pdf">Exporter en PDF</string>
|
<string name="export_pdf">Exporter en PDF</string>
|
||||||
<string name="filename">Nom de fichier</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="grant_permission">Autoriser</string>
|
||||||
<string name="last_saved_documents">Derniers documents enregistrés</string>
|
<string name="last_saved_documents">Derniers documents enregistrés</string>
|
||||||
<string name="libraries">Bibliothèques</string>
|
<string name="libraries">Bibliothèques</string>
|
||||||
@@ -27,7 +30,7 @@
|
|||||||
<string name="license">Licence</string>
|
<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="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">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">Ouvrir</string>
|
||||||
<string name="open_pdf">Ouvrir le PDF</string>
|
<string name="open_pdf">Ouvrir le PDF</string>
|
||||||
<string name="pdf_saved_to">PDF enregistré dans %1$s</string>
|
<string name="pdf_saved_to">PDF enregistré dans %1$s</string>
|
||||||
|
|||||||
@@ -10,16 +10,19 @@
|
|||||||
<string name="clear_text">Clear text</string>
|
<string name="clear_text">Clear text</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="close_document">Close document</string>
|
<string name="close_document">Close document</string>
|
||||||
|
<string name="creating_pdf">Creating PDF…</string>
|
||||||
<string name="current_document">Current document</string>
|
<string name="current_document">Current document</string>
|
||||||
<string name="delete_page">Delete page</string>
|
<string name="delete_page">Delete page</string>
|
||||||
<string name="delete_page_warning">Do you want to delete this page?</string>
|
<string name="delete_page_warning">Do you want to delete this page?</string>
|
||||||
<string name="document">Document</string>
|
<string name="document">Document</string>
|
||||||
|
<string name="end_scan">End scan</string>
|
||||||
<string name="error">Error: %1$s</string>
|
<string name="error">Error: %1$s</string>
|
||||||
<string name="error_no_document">No document detected</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_no_pdf_app">No app found to open PDF</string>
|
||||||
<string name="error_save">Failed to save PDF</string>
|
<string name="error_save">Failed to save PDF</string>
|
||||||
<string name="export_pdf">Export PDF</string>
|
<string name="export_pdf">Export PDF</string>
|
||||||
<string name="filename">Filename</string>
|
<string name="filename">Filename</string>
|
||||||
|
<string name="file_size">File size: %1$s</string>
|
||||||
<string name="grant_permission">Grant permission</string>
|
<string name="grant_permission">Grant permission</string>
|
||||||
<string name="last_saved_documents">Last saved documents</string>
|
<string name="last_saved_documents">Last saved documents</string>
|
||||||
<string name="libraries">Libraries</string>
|
<string name="libraries">Libraries</string>
|
||||||
@@ -28,7 +31,7 @@
|
|||||||
<string name="license">License</string>
|
<string name="license">License</string>
|
||||||
<string name="licensed_under">This application is licensed under the GNU General Public License v3.0.</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">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">Open</string>
|
||||||
<string name="open_pdf">Open PDF</string>
|
<string name="open_pdf">Open PDF</string>
|
||||||
<string name="pdf_saved_to">PDF saved to %1$s</string>
|
<string name="pdf_saved_to">PDF saved to %1$s</string>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.fairscan.app
|
|||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.fairscan.app.Screen.Main.Camera
|
import org.fairscan.app.Screen.Main.Camera
|
||||||
import org.fairscan.app.Screen.Main.Document
|
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.Main.Home
|
||||||
import org.fairscan.app.Screen.Overlay.About
|
import org.fairscan.app.Screen.Overlay.About
|
||||||
import org.fairscan.app.Screen.Overlay.Libraries
|
import org.fairscan.app.Screen.Overlay.Libraries
|
||||||
@@ -36,10 +37,12 @@ class NavigationTest {
|
|||||||
val atHome = NavigationState.initial()
|
val atHome = NavigationState.initial()
|
||||||
val atCamera = atHome.navigateTo(Camera)
|
val atCamera = atHome.navigateTo(Camera)
|
||||||
val atDocument = atHome.navigateTo(Document())
|
val atDocument = atHome.navigateTo(Document())
|
||||||
|
val atExport = atHome.navigateTo(Export)
|
||||||
|
|
||||||
assertThat(atHome.current).isEqualTo(Home)
|
assertThat(atHome.current).isEqualTo(Home)
|
||||||
assertThat(atCamera.current).isEqualTo(Camera)
|
assertThat(atCamera.current).isEqualTo(Camera)
|
||||||
assertThat(atDocument.current).isEqualTo(Document())
|
assertThat(atDocument.current).isEqualTo(Document())
|
||||||
|
assertThat(atExport.current).isEqualTo(Export)
|
||||||
|
|
||||||
assertThat(atCamera.navigateTo(Document())).isEqualTo(atDocument)
|
assertThat(atCamera.navigateTo(Document())).isEqualTo(atDocument)
|
||||||
assertThat(atDocument.navigateTo(Home)).isEqualTo(atHome)
|
assertThat(atDocument.navigateTo(Home)).isEqualTo(atHome)
|
||||||
@@ -48,6 +51,7 @@ class NavigationTest {
|
|||||||
assertThat(atHome.navigateBack()).isEqualTo(atHome)
|
assertThat(atHome.navigateBack()).isEqualTo(atHome)
|
||||||
assertThat(atCamera.navigateBack()).isEqualTo(atHome)
|
assertThat(atCamera.navigateBack()).isEqualTo(atHome)
|
||||||
assertThat(atDocument.navigateBack()).isEqualTo(atCamera)
|
assertThat(atDocument.navigateBack()).isEqualTo(atCamera)
|
||||||
|
assertThat(atExport.navigateBack()).isEqualTo(atDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user