Multi-page step 1: PageValidationDialog

This commit is contained in:
Pierre-Yves Nicolas
2025-06-02 16:16:05 +02:00
parent 86f5b27093
commit 0b76c3fc1e
3 changed files with 120 additions and 8 deletions

View File

@@ -3,7 +3,6 @@ package org.mydomain.myscan
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Matrix import android.graphics.Matrix
import android.util.Log
import androidx.camera.core.ImageProxy import androidx.camera.core.ImageProxy
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@@ -68,13 +67,10 @@ class MainViewModel(private val imageSegmentationService: ImageSegmentationServi
_currentScreen.value = screen _currentScreen.value = screen
} }
fun processCapturedImageAndNavigate(imageProxy: ImageProxy) { fun processCapturedImageThen(imageProxy: ImageProxy, onResult: (Bitmap?) -> Unit) {
viewModelScope.launch { viewModelScope.launch {
Log.d("MyScan", "Navigating to spinner")
navigateTo(Screen.PagePreview(image = null, isProcessing = true))
val processedImage = processCapturedImage(imageProxy) val processedImage = processCapturedImage(imageProxy)
Log.d("MyScan", "Navigating to result image") onResult(processedImage)
navigateTo(Screen.PagePreview(image = processedImage, isProcessing = false))
} }
} }
@@ -98,4 +94,8 @@ class MainViewModel(private val imageSegmentationService: ImageSegmentationServi
val matrix = Matrix().apply { postRotate(degrees.toFloat()) } val matrix = Matrix().apply { postRotate(degrees.toFloat()) }
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true) return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
} }
fun addPage(bitmap: Bitmap) {
// TODO
}
} }

View File

@@ -64,6 +64,12 @@ fun CameraScreen(
uiState: CameraScreenState, uiState: CameraScreenState,
onImageAnalyzed: (ImageProxy) -> Unit, onImageAnalyzed: (ImageProxy) -> Unit,
) { ) {
// TODO Should we move those variables to ViewModel?
// TODO pause the live analysis when displaying the PageValidationDialogs
val showPageDialog = remember { mutableStateOf(false) }
val isProcessing = remember { mutableStateOf(false) }
val processedPage = remember { mutableStateOf<Bitmap?>(null) }
val context = LocalContext.current val context = LocalContext.current
val requestPermissionLauncher = rememberLauncherForActivityResult( val requestPermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
@@ -101,10 +107,15 @@ fun CameraScreen(
MessageBox(uiState.inferenceTime) MessageBox(uiState.inferenceTime)
Button( Button(
onClick = { onClick = {
showPageDialog.value = true
isProcessing.value = true
captureController.takePicture( captureController.takePicture(
onImageCaptured = { imageProxy -> onImageCaptured = { imageProxy ->
if (imageProxy != null) { if (imageProxy != null) {
viewModel.processCapturedImageAndNavigate(imageProxy) viewModel.processCapturedImageThen(imageProxy) { result ->
processedPage.value = result
isProcessing.value = false
}
} else { } else {
Log.e("MyScan", "Error during image capture") Log.e("MyScan", "Error during image capture")
} }
@@ -115,6 +126,23 @@ fun CameraScreen(
Text("Capture") Text("Capture")
} }
} }
if (showPageDialog.value) {
PageValidationDialog(
isProcessing = isProcessing.value,
pageBitmap = processedPage.value,
onConfirm = {
viewModel.addPage(processedPage.value!!)
showPageDialog.value = false
},
onReject = {
showPageDialog.value = false
},
onDismiss = {
showPageDialog.value = false
}
)
}
} }
@Composable @Composable

View File

@@ -0,0 +1,84 @@
package org.mydomain.myscan.view
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun PageValidationDialog(
isProcessing: Boolean,
pageBitmap: Bitmap?,
onConfirm: () -> Unit,
onReject: () -> Unit,
onDismiss: () -> Unit
) {
Dialog (onDismissRequest = onDismiss) {
Surface (
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.surface,
tonalElevation = 8.dp,
modifier = Modifier
.fillMaxSize()
.aspectRatio(3f / 4f)
) {
if (isProcessing) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column (
Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
if (pageBitmap == null) {
Text("Failed to process image")
} else {
Image(
bitmap = pageBitmap.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentScale = ContentScale.Fit
)
}
Row (
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
OutlinedButton (onClick = onReject) {
Text("Reject")
}
Button (onClick = onConfirm) {
Text("OK")
}
}
}
}
}
}
}