DocumentScreen: pages in LazyColumn

This commit is contained in:
Pierre-Yves Nicolas
2025-06-07 05:23:56 +02:00
committed by pynicolas
parent b3d13d98bc
commit 5292f5bdb3
5 changed files with 44 additions and 60 deletions

View File

@@ -55,7 +55,6 @@ dependencies {
implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.accompanist.flowlayout)
implementation(libs.androidx.camera.core) implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.lifecycle)

View File

@@ -35,7 +35,7 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mydomain.myscan.ui.theme.MyScanTheme import org.mydomain.myscan.ui.theme.MyScanTheme
import org.mydomain.myscan.view.CameraScreen import org.mydomain.myscan.view.CameraScreen
import org.mydomain.myscan.view.FinalizeDocumentScreen import org.mydomain.myscan.view.DocumentScreen
import org.opencv.android.OpenCVLoader import org.opencv.android.OpenCVLoader
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -54,17 +54,21 @@ class MainActivity : ComponentActivity() {
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle() val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
MyScanTheme { MyScanTheme {
Scaffold { innerPadding ->
Column (modifier = Modifier.padding(innerPadding)) { Column {
when (currentScreen) { when (currentScreen) {
is Screen.Camera -> { is Screen.Camera -> {
CameraScreen(viewModel, liveAnalysisState, Scaffold { innerPadding->
CameraScreen(
viewModel, liveAnalysisState,
onImageAnalyzed = { image -> viewModel.segment(image) }, onImageAnalyzed = { image -> viewModel.segment(image) },
onFinalizePressed = { viewModel.navigateTo(Screen.FinalizeDocument) } onFinalizePressed = { viewModel.navigateTo(Screen.FinalizeDocument) },
modifier = Modifier.padding(innerPadding)
) )
} }
}
is Screen.FinalizeDocument -> { is Screen.FinalizeDocument -> {
FinalizeDocumentScreen ( DocumentScreen (
pageIds, pageIds,
imageLoader = { id -> viewModel.getBitmap(id) }, imageLoader = { id -> viewModel.getBitmap(id) },
onBackPressed = { viewModel.navigateTo(Screen.Camera) }, onBackPressed = { viewModel.navigateTo(Screen.Camera) },
@@ -78,7 +82,6 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
}
private fun sharePdf( private fun sharePdf(
viewModel: MainViewModel, viewModel: MainViewModel,

View File

@@ -84,7 +84,8 @@ fun CameraScreen(
viewModel: MainViewModel, viewModel: MainViewModel,
liveAnalysisState: LiveAnalysisState, liveAnalysisState: LiveAnalysisState,
onImageAnalyzed: (ImageProxy) -> Unit, onImageAnalyzed: (ImageProxy) -> Unit,
onFinalizePressed: () -> Unit onFinalizePressed: () -> Unit,
modifier: Modifier,
) { ) {
// TODO pause the live analysis when displaying the PageValidationDialogs // TODO pause the live analysis when displaying the PageValidationDialogs
val showPageDialog = rememberSaveable { mutableStateOf(false) } val showPageDialog = rememberSaveable { mutableStateOf(false) }
@@ -112,7 +113,7 @@ fun CameraScreen(
} }
} }
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = modifier.fillMaxSize()) {
CameraPreviewWithOverlay(onImageAnalyzed, captureController, liveAnalysisState) CameraPreviewWithOverlay(onImageAnalyzed, captureController, liveAnalysisState)
MessageBox(liveAnalysisState.inferenceTime) MessageBox(liveAnalysisState.inferenceTime)
Button( Button(

View File

@@ -18,11 +18,8 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -31,13 +28,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -48,10 +42,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -60,7 +54,7 @@ import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FinalizeDocumentScreen( fun DocumentScreen(
pageIds: List<String>, pageIds: List<String>,
imageLoader: (String) -> Bitmap, imageLoader: (String) -> Bitmap,
onBackPressed: () -> Unit, onBackPressed: () -> Unit,
@@ -71,6 +65,10 @@ fun FinalizeDocumentScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopAppBar( TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = { Text("Finalize document") }, title = { Text("Finalize document") },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBackPressed) { IconButton(onClick = onBackPressed) {
@@ -81,7 +79,7 @@ fun FinalizeDocumentScreen(
}, },
bottomBar = { bottomBar = {
BottomAppBar( BottomAppBar(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) containerColor = Color.Transparent
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -108,50 +106,35 @@ private fun DocumentPreview(
imageLoader: (String) -> Bitmap, imageLoader: (String) -> Bitmap,
onDeleteImage: (String) -> Unit, onDeleteImage: (String) -> Unit,
) { ) {
Column( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .background(MaterialTheme.colorScheme.surfaceContainer)
.padding(padding) .padding(PaddingValues(top = padding.calculateTopPadding()))
) { ) {
Text( Text(
"Pages", "${pageIds.size} pages",
modifier = Modifier.padding(start = 16.dp, top = 16.dp), modifier = Modifier.padding(start = 16.dp, top = 16.dp).align(Alignment.TopStart),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
FlowRow( LazyColumn (
contentPadding = PaddingValues(20.dp),
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.fillMaxWidth(), .align(Alignment.Center),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
pageIds.forEachIndexed { index, id -> items(pageIds) { id ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
// TODO Display small images rather than big ones // TODO Display small images rather than big ones
// TODO Make it possible to zoom on an image // TODO Make it possible to zoom on an image
Box { val bitmap = imageLoader(id).asImageBitmap()
Image( Image(
bitmap = imageLoader(id).asImageBitmap(), bitmap = bitmap,
contentDescription = "Page ${index + 1}", contentDescription = null,
modifier = Modifier modifier = Modifier
.size(160.dp) .size(200.dp)
.padding(4.dp) .padding(4.dp)
.clip(RoundedCornerShape(8.dp))
.border(1.dp, Color.DarkGray)
) )
IconButton(
onClick = { onDeleteImage(id) },
modifier = Modifier
.align(Alignment.TopEnd)
.size(24.dp)
.background(Color.Black.copy(alpha = 0.5f), shape = CircleShape)
) {
Icon(Icons.Default.Delete, contentDescription = "Delete", tint = Color.White)
}
}
}
} }
} }
} }
@@ -161,7 +144,7 @@ private fun DocumentPreview(
@Preview @Preview
fun DocumentScreenPreview() { fun DocumentScreenPreview() {
val context = LocalContext.current val context = LocalContext.current
FinalizeDocumentScreen( DocumentScreen(
pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" }, pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
imageLoader = { id -> imageLoader = { id ->
context.assets.open(id).use { input -> context.assets.open(id).use { input ->

View File

@@ -12,7 +12,6 @@ composeBom = "2025.05.00"
camerax = "1.4.2" camerax = "1.4.2"
litert = "1.2.0" litert = "1.2.0"
opencv = "4.11.0" opencv = "4.11.0"
flowlayout = "0.36.0"
assertj = "3.27.3" assertj = "3.27.3"
pdfbox = "2.0.27.0" pdfbox = "2.0.27.0"
@@ -34,7 +33,6 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "flowlayout" }
androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" } androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" } androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" } androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }