Skip to content

Commit 76a9417

Browse files
committed
Move Save & Share to a new Export screen
1 parent 7b125b0 commit 76a9417

File tree

12 files changed

+226
-218
lines changed

12 files changed

+226
-218
lines changed

app/src/main/java/org/fairscan/app/MainActivity.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import org.fairscan.app.ui.theme.MyScanTheme
4141
import org.fairscan.app.view.AboutScreen
4242
import org.fairscan.app.view.CameraScreen
4343
import org.fairscan.app.view.DocumentScreen
44+
import org.fairscan.app.view.ExportScreenWrapper
4445
import org.fairscan.app.view.HomeScreen
4546
import org.fairscan.app.view.LibrariesScreen
4647
import org.opencv.android.OpenCVLoader
@@ -67,6 +68,7 @@ class MainActivity : ComponentActivity() {
6768
toHomeScreen = { viewModel.navigateTo(Screen.Main.Home) },
6869
toCameraScreen = { viewModel.navigateTo(Screen.Main.Camera) },
6970
toDocumentScreen = { viewModel.navigateTo(Screen.Main.Document()) },
71+
toExportScreen = { viewModel.navigateTo(Screen.Main.Export) },
7072
toAboutScreen = { viewModel.navigateTo(Screen.Overlay.About) },
7173
toLibrariesScreen = { viewModel.navigateTo(Screen.Overlay.Libraries) },
7274
back = { viewModel.navigateBack() }
@@ -97,21 +99,26 @@ class MainActivity : ComponentActivity() {
9799
DocumentScreen (
98100
document = document,
99101
initialPage = screen.initialPage,
102+
navigation = navigation,
103+
onDeleteImage = { id -> viewModel.deletePage(id) },
104+
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
105+
)
106+
}
107+
is Screen.Main.Export -> {
108+
ExportScreenWrapper(
100109
navigation = navigation,
101110
pdfActions = PdfGenerationActions(
102111
startGeneration = viewModel::startPdfGeneration,
103-
cancelGeneration = viewModel::cancelPdfGeneration,
104112
setFilename = viewModel::setFilename,
105113
uiStateFlow = viewModel.pdfUiState,
106-
sharePdf = { sharePdf(viewModel.getFinalPdf()) },
114+
sharePdf = { sharePdf(viewModel.getFinalPdf(), viewModel) },
107115
savePdf = { savePdf(viewModel.getFinalPdf(), viewModel) },
108116
openPdf = { openPdf(viewModel.pdfUiState.value.savedFileUri) }
109117
),
110-
onStartNew = {
118+
onCloseScan = {
111119
viewModel.startNewDocument()
112-
viewModel.navigateTo(Screen.Main.Home) },
113-
onDeleteImage = { id -> viewModel.deletePage(id) },
114-
onRotateImage = { id, clockwise -> viewModel.rotateImage(id, clockwise) }
120+
viewModel.navigateTo(Screen.Main.Home)
121+
},
115122
)
116123
}
117124
is Screen.Overlay.About -> {
@@ -125,9 +132,10 @@ class MainActivity : ComponentActivity() {
125132
}
126133
}
127134

128-
private fun sharePdf(generatedPdf: GeneratedPdf?) {
135+
private fun sharePdf(generatedPdf: GeneratedPdf?, viewModel: MainViewModel) {
129136
if (generatedPdf == null)
130137
return
138+
viewModel.setPdfAsShared()
131139
val file = generatedPdf.file
132140
val authority = "${applicationContext.packageName}.fileprovider"
133141
val fileUri = FileProvider.getUriForFile(this, authority, file)

app/src/main/java/org/fairscan/app/MainViewModel.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,7 @@ class MainViewModel(
268268
}
269269

270270
fun startPdfGeneration() {
271-
val currentState = _pdfUiState.value
272-
if (currentState.isGenerating || currentState.generatedPdf != null) return
273-
274-
_pdfUiState.update { it.copy(isGenerating = true, errorMessage = null) }
275-
271+
cancelPdfGeneration()
276272
generationJob = viewModelScope.launch {
277273
try {
278274
val result = generatePdf()
@@ -299,6 +295,10 @@ class MainViewModel(
299295
_pdfUiState.value = PdfGenerationUiState()
300296
}
301297

298+
fun setPdfAsShared() {
299+
_pdfUiState.update { it.copy(hasSharedPdf = true) }
300+
}
301+
302302
fun getFinalPdf(): GeneratedPdf? {
303303
val tempPdf = _pdfUiState.value.generatedPdf ?: return null
304304
val tempFile = tempPdf.file
@@ -374,7 +374,6 @@ data class GeneratedPdf(
374374
// TODO Move somewhere else: ViewModel should not depend on that
375375
data class PdfGenerationActions(
376376
val startGeneration: () -> Unit,
377-
val cancelGeneration: () -> Unit,
378377
val setFilename: (String) -> Unit,
379378
val uiStateFlow: StateFlow<PdfGenerationUiState>,// TODO is it ok to have that here?
380379
val sharePdf: () -> Unit,

app/src/main/java/org/fairscan/app/Navigation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ sealed class Screen {
1919
object Home : Main()
2020
object Camera : Main()
2121
data class Document(val initialPage: Int = 0) : Main()
22+
object Export : Main()
2223
}
2324
sealed class Overlay : Screen() {
2425
object About : Overlay()
@@ -30,6 +31,7 @@ data class Navigation(
3031
val toHomeScreen: () -> Unit,
3132
val toCameraScreen: () -> Unit,
3233
val toDocumentScreen: () -> Unit,
34+
val toExportScreen: () -> Unit,
3335
val toAboutScreen: () -> Unit,
3436
val toLibrariesScreen: () -> Unit,
3537
val back: () -> Unit,
@@ -57,6 +59,7 @@ data class NavigationState private constructor(val stack: List<Screen>) {
5759
is Screen.Main.Home -> this // Back handled by system
5860
is Screen.Main.Camera -> copy(stack = listOf(Screen.Main.Home))
5961
is Screen.Main.Document -> copy(stack = listOf(Screen.Main.Camera))
62+
is Screen.Main.Export -> copy(stack = listOf(Screen.Main.Document()))
6063
is Screen.Overlay -> copy(stack = stack.dropLast(1))
6164
}
6265
}

app/src/main/java/org/fairscan/app/ui/UiState.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ data class PdfGenerationUiState(
2424
val desiredFilename: String = "",
2525
val savedFileUri: Uri? = null,
2626
val saveDirectoryName: String? = null,
27-
val errorMessage: String? = null
28-
)
27+
val hasSharedPdf: Boolean = false,
28+
val errorMessage: String? = null,
29+
) {
30+
val hasSavedOrSharedPdf get() = savedFileUri != null || hasSharedPdf
31+
}
2932

3033
data class RecentDocumentUiState(
3134
val file: File,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2025 Pierre-Yves Nicolas
3+
*
4+
* This program is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option)
7+
* any later version.
8+
* This program is distributed in the hope that it will be useful, but WITHOUT
9+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11+
* more details.
12+
* You should have received a copy of the GNU General Public License along with
13+
* this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
package org.fairscan.app.view
16+
17+
import androidx.compose.material3.AlertDialog
18+
import androidx.compose.material3.Text
19+
import androidx.compose.material3.TextButton
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.MutableState
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.text.font.FontWeight
24+
import org.fairscan.app.R
25+
26+
@Composable
27+
fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>, title: String) {
28+
ConfirmationDialog(title, stringResource(R.string.new_document_warning), showDialog, onConfirm)
29+
}
30+
31+
@Composable
32+
fun ConfirmationDialog(
33+
title: String,
34+
message: String,
35+
showDialog: MutableState<Boolean>,
36+
onConfirm: () -> Unit,
37+
) {
38+
AlertDialog(
39+
title = { Text(title) },
40+
text = { Text(message) },
41+
confirmButton = {
42+
TextButton(onClick = {
43+
showDialog.value = false
44+
onConfirm()
45+
}) {
46+
Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold)
47+
}
48+
},
49+
dismissButton = {
50+
TextButton(onClick = { showDialog.value = false }) {
51+
Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold)
52+
}
53+
},
54+
onDismissRequest = { showDialog.value = false },
55+
)
56+
}

app/src/main/java/org/fairscan/app/view/DocumentScreen.kt

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,14 @@ import androidx.compose.material.icons.Icons
3232
import androidx.compose.material.icons.automirrored.filled.RotateLeft
3333
import androidx.compose.material.icons.automirrored.filled.RotateRight
3434
import androidx.compose.material.icons.filled.Add
35-
import androidx.compose.material.icons.filled.Close
3635
import androidx.compose.material.icons.filled.PictureAsPdf
3736
import androidx.compose.material.icons.outlined.Delete
38-
import androidx.compose.material3.AlertDialog
3937
import androidx.compose.material3.ExperimentalMaterial3Api
4038
import androidx.compose.material3.MaterialTheme
4139
import androidx.compose.material3.Text
42-
import androidx.compose.material3.TextButton
4340
import androidx.compose.runtime.Composable
4441
import androidx.compose.runtime.LaunchedEffect
4542
import androidx.compose.runtime.MutableIntState
46-
import androidx.compose.runtime.MutableState
4743
import androidx.compose.runtime.mutableIntStateOf
4844
import androidx.compose.runtime.mutableStateOf
4945
import androidx.compose.runtime.saveable.rememberSaveable
@@ -53,16 +49,12 @@ import androidx.compose.ui.geometry.Size
5349
import androidx.compose.ui.graphics.asImageBitmap
5450
import androidx.compose.ui.platform.LocalContext
5551
import androidx.compose.ui.res.stringResource
56-
import androidx.compose.ui.text.font.FontWeight
5752
import androidx.compose.ui.tooling.preview.Preview
5853
import androidx.compose.ui.unit.dp
59-
import kotlinx.coroutines.flow.MutableStateFlow
6054
import net.engawapg.lib.zoomable.rememberZoomState
6155
import net.engawapg.lib.zoomable.zoomable
6256
import org.fairscan.app.Navigation
63-
import org.fairscan.app.PdfGenerationActions
6457
import org.fairscan.app.R
65-
import org.fairscan.app.ui.PdfGenerationUiState
6658
import org.fairscan.app.ui.theme.MyScanTheme
6759

6860
@OptIn(ExperimentalMaterial3Api::class)
@@ -71,15 +63,11 @@ fun DocumentScreen(
7163
document: DocumentUiModel,
7264
initialPage: Int,
7365
navigation: Navigation,
74-
pdfActions: PdfGenerationActions,
75-
onStartNew: () -> Unit,
7666
onDeleteImage: (String) -> Unit,
7767
onRotateImage: (String, Boolean) -> Unit,
7868
) {
7969
// TODO Check how often images are loaded
80-
val showNewDocDialog = rememberSaveable { mutableStateOf(false) }
8170
val showDeletePageDialog = rememberSaveable { mutableStateOf(false) }
82-
val showPdfDialog = rememberSaveable { mutableStateOf(false) }
8371
val currentPageIndex = rememberSaveable { mutableIntStateOf(initialPage) }
8472
if (currentPageIndex.intValue >= document.pageCount()) {
8573
currentPageIndex.intValue = document.pageCount() - 1
@@ -105,7 +93,7 @@ fun DocumentScreen(
10593
),
10694
onBack = navigation.back,
10795
bottomBar = {
108-
BottomBar(showPdfDialog, showNewDocDialog)
96+
BottomBar(navigation)
10997
},
11098
pageListButton = {
11199
SecondaryActionButton(
@@ -121,22 +109,13 @@ fun DocumentScreen(
121109
{ showDeletePageDialog.value = true },
122110
onRotateImage,
123111
modifier)
124-
if (showNewDocDialog.value) {
125-
NewDocumentDialog(onConfirm = onStartNew, showNewDocDialog, stringResource(R.string.close_document))
126-
}
127112
if (showDeletePageDialog.value) {
128113
ConfirmationDialog(
129114
title = stringResource(R.string.delete_page),
130115
message = stringResource(R.string.delete_page_warning),
131116
showDialog = showDeletePageDialog
132117
) { onDeleteImage(document.pageId(currentPageIndex.intValue)) }
133118
}
134-
if (showPdfDialog.value) {
135-
PdfGenerationBottomSheetWrapper(
136-
onDismiss = { showPdfDialog.value = false },
137-
pdfActions = pdfActions,
138-
)
139-
}
140119
}
141120
}
142121

@@ -226,61 +205,21 @@ fun RotationButtons(
226205

227206
@Composable
228207
private fun BottomBar(
229-
showPdfDialog: MutableState<Boolean>,
230-
showNewDocDialog: MutableState<Boolean>,
208+
navigation: Navigation,
231209
) {
232210
Row(
233211
modifier = Modifier.fillMaxWidth(),
234212
verticalAlignment = Alignment.CenterVertically,
235213
horizontalArrangement = Arrangement.End
236214
) {
237215
MainActionButton(
238-
onClick = { showPdfDialog.value = true },
216+
onClick = navigation.toExportScreen,
239217
icon = Icons.Default.PictureAsPdf,
240218
text = stringResource(R.string.export_pdf),
241219
)
242-
Spacer(modifier = Modifier.width(8.dp))
243-
SecondaryActionButton(
244-
icon = Icons.Default.Close,
245-
contentDescription = stringResource(R.string.close_document),
246-
onClick = { showNewDocDialog.value = true },
247-
modifier = Modifier.padding(vertical = 8.dp)
248-
)
249220
}
250221
}
251222

252-
@Composable
253-
fun NewDocumentDialog(onConfirm: () -> Unit, showDialog: MutableState<Boolean>, title: String) {
254-
ConfirmationDialog(title, stringResource(R.string.new_document_warning), showDialog, onConfirm)
255-
}
256-
257-
@Composable
258-
private fun ConfirmationDialog(
259-
title: String,
260-
message: String,
261-
showDialog: MutableState<Boolean>,
262-
onConfirm: () -> Unit,
263-
) {
264-
AlertDialog(
265-
title = { Text(title) },
266-
text = { Text(message) },
267-
confirmButton = {
268-
TextButton(onClick = {
269-
showDialog.value = false
270-
onConfirm()
271-
}) {
272-
Text(stringResource(R.string.yes), fontWeight = FontWeight.Bold)
273-
}
274-
},
275-
dismissButton = {
276-
TextButton(onClick = { showDialog.value = false }) {
277-
Text(stringResource(R.string.cancel), fontWeight = FontWeight.Bold)
278-
}
279-
},
280-
onDismissRequest = { showDialog.value = false },
281-
)
282-
}
283-
284223
@Composable
285224
@Preview
286225
fun DocumentScreenPreview() {
@@ -291,11 +230,6 @@ fun DocumentScreenPreview() {
291230
LocalContext.current),
292231
initialPage = 1,
293232
navigation = dummyNavigation(),
294-
pdfActions = PdfGenerationActions(
295-
{}, {}, {},
296-
MutableStateFlow(PdfGenerationUiState()),
297-
{}, {}, {}),
298-
onStartNew = {},
299233
onDeleteImage = { _ -> },
300234
onRotateImage = { _,_ -> },
301235
)

0 commit comments

Comments
 (0)