Home screen: change layout to focus on "New scan"
This commit is contained in:
@@ -80,7 +80,7 @@ class MainActivity : ComponentActivity() {
|
||||
cameraPermission = cameraPermission,
|
||||
currentDocument = document,
|
||||
navigation = navigation,
|
||||
onStartNewScan = navigation.toCameraScreen,
|
||||
onClearScan = { viewModel.startNewDocument() },
|
||||
recentDocuments = recentDocs,
|
||||
onOpenPdf = { file -> openPdf(file.toUri()) }
|
||||
)
|
||||
|
||||
@@ -355,7 +355,7 @@ class MainViewModel(
|
||||
current.toBuilder()
|
||||
.addDocuments(0, newDoc)
|
||||
.also { builder ->
|
||||
while (builder.documentsCount > 10) {
|
||||
while (builder.documentsCount > 3) {
|
||||
builder.removeDocuments(builder.documentsCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,29 +23,34 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material.icons.filled.PhotoCamera
|
||||
import androidx.compose.material.icons.filled.PictureAsPdf
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -65,11 +70,10 @@ fun HomeScreen(
|
||||
cameraPermission: CameraPermissionState,
|
||||
currentDocument: DocumentUiModel,
|
||||
navigation: Navigation,
|
||||
onStartNewScan: () -> Unit,
|
||||
onClearScan: () -> Unit,
|
||||
recentDocuments: List<RecentDocumentUiState>,
|
||||
onOpenPdf: (File) -> Unit,
|
||||
) {
|
||||
val showCloseDocDialog = rememberSaveable { mutableStateOf(false) }
|
||||
Scaffold (
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -79,25 +83,6 @@ fun HomeScreen(
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
BottomAppBar {
|
||||
Spacer(Modifier.weight(1f))
|
||||
MainActionButton(
|
||||
onClick = {
|
||||
if (currentDocument.isEmpty()) {
|
||||
onStartNewScan()
|
||||
} else {
|
||||
showCloseDocDialog.value = true
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.PhotoCamera,
|
||||
text = stringResource(R.string.start_a_new_scan),
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.height(48.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
Column (
|
||||
modifier = Modifier
|
||||
@@ -105,26 +90,31 @@ fun HomeScreen(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
if (!cameraPermission.isGranted) {
|
||||
CameraPermissionRationale(cameraPermission)
|
||||
} else {
|
||||
ScanButton(
|
||||
onClick = {
|
||||
onClearScan()
|
||||
navigation.toCameraScreen()
|
||||
},
|
||||
Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
if (!currentDocument.isEmpty()) {
|
||||
SectionTitle(stringResource(R.string.current_document))
|
||||
CurrentDocumentCard(currentDocument, navigation)
|
||||
}
|
||||
|
||||
if (recentDocuments.isNotEmpty()) {
|
||||
SectionTitle(stringResource(R.string.last_saved_documents))
|
||||
OngoingScanBanner(
|
||||
currentDocument,
|
||||
onResumeScan = navigation.toDocumentScreen,
|
||||
onClearScan = onClearScan,
|
||||
)
|
||||
} else if (recentDocuments.isNotEmpty()) {
|
||||
RecentDocumentList(recentDocuments, onOpenPdf)
|
||||
}
|
||||
|
||||
if (showCloseDocDialog.value) {
|
||||
NewDocumentDialog(
|
||||
onConfirm = onStartNewScan,
|
||||
showCloseDocDialog,
|
||||
stringResource(R.string.new_document))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,10 +129,12 @@ private fun CameraPermissionRationale(cameraPermission: CameraPermissionState) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
stringResource(R.string.camera_permission_rationale),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Button(onClick = { cameraPermission.request() }) {
|
||||
Button(
|
||||
onClick = { cameraPermission.request() },
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
) {
|
||||
Text(stringResource(R.string.grant_permission))
|
||||
}
|
||||
}
|
||||
@@ -150,33 +142,82 @@ private fun CameraPermissionRationale(cameraPermission: CameraPermissionState) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrentDocumentCard(
|
||||
currentDocument: DocumentUiModel,
|
||||
navigation: Navigation,
|
||||
fun ScanButton(onClick: () -> Unit, modifier: Modifier) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier.padding(32.dp),
|
||||
elevation = ButtonDefaults.buttonElevation(defaultElevation = 6.dp)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
Icon(
|
||||
imageVector = Icons.Default.PhotoCamera,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.scan_button),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OngoingScanBanner(
|
||||
currentDocument: DocumentUiModel,
|
||||
onResumeScan: () -> Unit,
|
||||
onClearScan: () -> Unit
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 2.dp,
|
||||
shadowElevation = 4.dp,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(12.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
currentDocument.load(0)?.let {
|
||||
Image(
|
||||
bitmap = it.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.height(100.dp)
|
||||
.padding(4.dp)
|
||||
.size(60.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(12.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.scan_in_progress),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
text = pageCountText(currentDocument.pageCount()),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onClearScan,
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.DeleteOutline,
|
||||
contentDescription = stringResource(R.string.discard_scan),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(pageCountText(currentDocument.pageCount()))
|
||||
Button(onClick = onResumeScan) {
|
||||
Text(stringResource(R.string.resume))
|
||||
}
|
||||
MainActionButton(navigation.toDocumentScreen, stringResource(R.string.open))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,8 +227,13 @@ private fun RecentDocumentList(
|
||||
recentDocuments: List<RecentDocumentUiState>,
|
||||
onOpenPdf: (File) -> Unit
|
||||
) {
|
||||
HorizontalDivider()
|
||||
Text(
|
||||
stringResource(R.string.last_saved_documents),
|
||||
modifier = Modifier.padding(start = 12.dp, top = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
Column {
|
||||
val maxListSize = 5
|
||||
val maxListSize = 3
|
||||
recentDocuments.subList(0, min(maxListSize, recentDocuments.size)).forEach { doc ->
|
||||
ListItem(
|
||||
headlineContent = { Text(doc.file.name) },
|
||||
@@ -202,29 +248,19 @@ private fun RecentDocumentList(
|
||||
},
|
||||
modifier = Modifier.clickable { onOpenPdf(doc.file) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionTitle(text: String) {
|
||||
Text(
|
||||
text,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(start = 12.dp, top = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun HomeScreenPreviewOnFirstLaunch() {
|
||||
MyScanTheme {
|
||||
HomeScreen(
|
||||
cameraPermission = rememberCameraPermissionState(),
|
||||
currentDocument = DocumentUiModel(listOf()) { _ -> null },
|
||||
currentDocument = fakeDocument(),
|
||||
navigation = dummyNavigation(),
|
||||
onStartNewScan = {},
|
||||
onClearScan = {},
|
||||
recentDocuments = listOf(),
|
||||
onOpenPdf = {},
|
||||
)
|
||||
@@ -241,7 +277,22 @@ fun HomeScreenPreviewWithCurrentDocument() {
|
||||
listOf("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||
LocalContext.current),
|
||||
navigation = dummyNavigation(),
|
||||
onStartNewScan = {},
|
||||
onClearScan = {},
|
||||
recentDocuments = listOf(),
|
||||
onOpenPdf = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun HomeScreenPreviewWithLastSavedFiles() {
|
||||
MyScanTheme {
|
||||
HomeScreen(
|
||||
cameraPermission = rememberCameraPermissionState(),
|
||||
currentDocument = fakeDocument(),
|
||||
navigation = dummyNavigation(),
|
||||
onClearScan = {},
|
||||
recentDocuments = listOf(
|
||||
RecentDocumentUiState(File("/path/my_file.pdf"), 1755971180000, 3),
|
||||
RecentDocumentUiState(File("/path/scan2.pdf"), 1755000500000, 1)
|
||||
|
||||
@@ -22,6 +22,10 @@ fun dummyNavigation(): Navigation {
|
||||
return Navigation({}, {}, {}, {}, {}, {}, {})
|
||||
}
|
||||
|
||||
fun fakeDocument(): DocumentUiModel {
|
||||
return DocumentUiModel(listOf()) { _ -> null }
|
||||
}
|
||||
|
||||
fun fakeDocument(pageIds: List<String>, context: Context): DocumentUiModel {
|
||||
return DocumentUiModel(pageIds) { id ->
|
||||
context.assets.open(id).use { input ->
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<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="discard_scan">Löschen</string>
|
||||
<string name="document">Dokument</string>
|
||||
<string name="end_scan">Scan beenden</string>
|
||||
<string name="error">Fehler: %1$s</string>
|
||||
@@ -34,10 +35,12 @@
|
||||
<string name="open">Öffnen</string>
|
||||
<string name="open_pdf">PDF öffnen</string>
|
||||
<string name="pdf_saved_to">PDF gespeichert unter %1$s</string>
|
||||
<string name="resume">Fortsetzen</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="scan_button">Neuer Scan</string>
|
||||
<string name="scan_in_progress">Scan läuft</string>
|
||||
<string name="share">Teilen</string>
|
||||
<string name="share_pdf">PDF teilen</string>
|
||||
<string name="start_a_new_scan">Neuen Scan starten</string>
|
||||
<string name="unknown_size">Unbekannte Größe</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="view_the_full_license">Vollständige Lizenz anzeigen</string>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<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="discard_scan">Supprimer le scan</string>
|
||||
<string name="document">Document</string>
|
||||
<string name="end_scan">Terminer le scan</string>
|
||||
<string name="error">Erreur : %1$s</string>
|
||||
@@ -34,10 +35,12 @@
|
||||
<string name="open">Ouvrir</string>
|
||||
<string name="open_pdf">Ouvrir le PDF</string>
|
||||
<string name="pdf_saved_to">PDF enregistré dans %1$s</string>
|
||||
<string name="resume">Reprendre</string>
|
||||
<string name="save">Enregistrer</string>
|
||||
<string name="scan_button">Nouveau scan</string>
|
||||
<string name="scan_in_progress">Scan en cours</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="share_pdf">Partager le PDF</string>
|
||||
<string name="start_a_new_scan">Nouveau scan</string>
|
||||
<string name="unknown_size">Taille inconnue</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="view_the_full_license">Voir la licence complète</string>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<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="discard_scan">Discard scan</string>
|
||||
<string name="document">Document</string>
|
||||
<string name="end_scan">End scan</string>
|
||||
<string name="error">Error: %1$s</string>
|
||||
@@ -35,10 +36,12 @@
|
||||
<string name="open">Open</string>
|
||||
<string name="open_pdf">Open PDF</string>
|
||||
<string name="pdf_saved_to">PDF saved to %1$s</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="scan_button">New Scan</string>
|
||||
<string name="scan_in_progress">Scan in progress</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="share_pdf">Share PDF</string>
|
||||
<string name="start_a_new_scan">Start a new scan</string>
|
||||
<string name="unknown_size">Unknown size</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="view_the_full_license">View the full license</string>
|
||||
|
||||
Reference in New Issue
Block a user