CameraScreen: LazyRow with list of pages

This commit is contained in:
Pierre-Yves Nicolas
2025-06-24 10:37:38 +02:00
parent e894c14260
commit ef2a49cf76

View File

@@ -27,6 +27,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
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.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -34,7 +35,10 @@ import androidx.compose.foundation.layout.height
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.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -48,6 +52,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
@@ -70,6 +75,7 @@ fun CameraScreen(
modifier: Modifier, modifier: Modifier,
) { ) {
var previewView by remember { mutableStateOf<PreviewView?>(null) } var previewView by remember { mutableStateOf<PreviewView?>(null) }
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
val captureController = remember { CameraCaptureController() } val captureController = remember { CameraCaptureController() }
DisposableEffect(Unit) { DisposableEffect(Unit) {
@@ -92,7 +98,8 @@ fun CameraScreen(
captureController = captureController, captureController = captureController,
onPreviewViewReady = { view -> previewView = view } onPreviewViewReady = { view -> previewView = view }
) }, ) },
pageCount = viewModel.pageCount(), pageIds = pageIds,
imageLoader = { id -> viewModel.getBitmap(id) },
liveAnalysisState = liveAnalysisState, liveAnalysisState = liveAnalysisState,
onCapture = { onCapture = {
Log.i("MyScan", "Pressed <Capture>") Log.i("MyScan", "Pressed <Capture>")
@@ -110,7 +117,8 @@ fun CameraScreen(
private fun CameraScreenContent( private fun CameraScreenContent(
modifier: Modifier, modifier: Modifier,
cameraPreview: @Composable () -> Unit, cameraPreview: @Composable () -> Unit,
pageCount: Int, pageIds: List<String>,
imageLoader: (String) -> Bitmap?,
liveAnalysisState: LiveAnalysisState, liveAnalysisState: LiveAnalysisState,
onCapture: () -> Unit, onCapture: () -> Unit,
onFinalizePressed: () -> Unit, onFinalizePressed: () -> Unit,
@@ -120,17 +128,20 @@ private fun CameraScreenContent(
CameraPreviewWithOverlay(cameraPreview, liveAnalysisState, captureState) CameraPreviewWithOverlay(cameraPreview, liveAnalysisState, captureState)
MessageBox(liveAnalysisState.inferenceTime) MessageBox(liveAnalysisState.inferenceTime)
Column (Modifier.align(Alignment.BottomCenter)) {
CaptureButton( CaptureButton(
onClick = onCapture, onClick = onCapture,
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.CenterHorizontally)
.padding(bottom = 96.dp) .padding(16.dp)
) )
CameraScreenFooter( CameraScreenFooter(
pageCount = pageCount, pageIds = pageIds,
imageLoader = imageLoader,
onFinalizePressed = onFinalizePressed, onFinalizePressed = onFinalizePressed,
modifier = Modifier.align(Alignment.BottomCenter) modifier = Modifier,
) )
}
captureState.processedImage?.let { captureState.processedImage?.let {
Surface ( Surface (
color = Color.Black.copy(alpha = 0.3f), color = Color.Black.copy(alpha = 0.3f),
@@ -213,15 +224,24 @@ fun MessageBox(inferenceTime: Long) {
@Composable @Composable
fun CameraScreenFooter( fun CameraScreenFooter(
pageCount: Int, pageIds: List<String>,
imageLoader: (String) -> Bitmap?,
onFinalizePressed: () -> Unit, onFinalizePressed: () -> Unit,
modifier: Modifier, modifier: Modifier,
) { ) {
val pageCount = pageIds.size
Surface ( Surface (
color = MaterialTheme.colorScheme.inverseOnSurface, color = MaterialTheme.colorScheme.inverseOnSurface,
tonalElevation = 4.dp, tonalElevation = 4.dp,
modifier = modifier.fillMaxWidth().height(56.dp) modifier = modifier
.fillMaxWidth()
.height(180.dp)
) { ) {
Column {
CameraCapturedPagesRow(
pageIds = pageIds,
imageLoader = imageLoader
)
Row ( Row (
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
@@ -230,7 +250,7 @@ fun CameraScreenFooter(
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( Text(
text = "Pages : $pageCount", text = "$pageCount pages",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
@@ -243,6 +263,43 @@ fun CameraScreenFooter(
} }
} }
} }
}
@Composable
fun CameraCapturedPagesRow(
pageIds: List<String>,
imageLoader: (String) -> Bitmap?
) {
if (pageIds.isEmpty()) return
LazyRow (
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(pageIds) { index, id ->
val image = imageLoader(id)
if (image != null) {
Box {
val bitmap = image.asImageBitmap()
val modifier =
if (bitmap.height > bitmap.width)
Modifier.height(120.dp)
else
Modifier.width(120.dp)
Image(
bitmap = bitmap,
contentDescription = "Page ${index + 1}",
modifier = modifier
.clip(RoundedCornerShape(4.dp))
)
}
}
}
}
}
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
@@ -258,6 +315,7 @@ fun CameraScreenPreviewWithProcessedImage() {
@Composable @Composable
private fun ScreenPreview(captureState: CaptureState) { private fun ScreenPreview(captureState: CaptureState) {
val context = LocalContext.current
MyScanTheme { MyScanTheme {
CameraScreenContent( CameraScreenContent(
modifier = Modifier, modifier = Modifier,
@@ -274,7 +332,12 @@ private fun ScreenPreview(captureState: CaptureState) {
) )
} }
}, },
pageCount = 3, pageIds = listOf(1, 2, 2, 2).map { "gallica.bnf.fr-bpt6k5530456s-$it.jpg" },
imageLoader = { id ->
context.assets.open(id).use { input ->
BitmapFactory.decodeStream(input)
}
},
liveAnalysisState = LiveAnalysisState(), liveAnalysisState = LiveAnalysisState(),
onCapture = {}, onCapture = {},
onFinalizePressed = {}, onFinalizePressed = {},