CameraScreen: improve UI for import

This commit is contained in:
Pierre-Yves Nicolas
2026-04-09 22:01:36 +02:00
parent b946913594
commit 6290717635
17 changed files with 161 additions and 10 deletions

View File

@@ -123,6 +123,7 @@ class MainActivity : ComponentActivity() {
val context = LocalContext.current
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
val liveAnalysisState by cameraViewModel.liveAnalysisState.collectAsStateWithLifecycle()
val importState by cameraViewModel.importState.collectAsStateWithLifecycle()
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
val documentUiState by viewModel.documentUiState.collectAsStateWithLifecycle()
val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle()
@@ -168,18 +169,20 @@ class MainActivity : ComponentActivity() {
}
is Screen.Main.Camera -> {
val pickMultiple = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(10)) { uris ->
if (uris.isNotEmpty()) cameraViewModel.importPhotos(uris)
ActivityResultContracts.PickMultipleVisualMedia(10)) {
uris -> cameraViewModel.importPhotos(uris)
}
CameraScreen(
viewModel,
cameraViewModel,
navigation,
liveAnalysisState,
importState,
onImageAnalyzed = { image -> cameraViewModel.liveAnalysis(image) },
onFinalizePressed = onExportClick,
cameraPermission = cameraPermission,
onImportClicked = {
cameraViewModel.onImportClicked()
pickMultiple.launch(PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly))
}

View File

@@ -43,6 +43,7 @@ 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.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
@@ -56,6 +57,7 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
@@ -86,7 +88,9 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -124,6 +128,7 @@ fun CameraScreen(
cameraViewModel: CameraViewModel,
navigation: Navigation,
liveAnalysisState: LiveAnalysisState,
importState: ImportState,
onImageAnalyzed: (ImageProxy) -> Unit,
onFinalizePressed: () -> Unit,
cameraPermission: CameraPermissionState,
@@ -211,6 +216,7 @@ fun CameraScreen(
document.pageCount(),
liveAnalysisState,
captureState,
importState,
showDetectionError,
isLandscape = isLandscape,
isDebugMode,
@@ -286,7 +292,11 @@ private fun CameraScreenScaffold(
onBack = navigation.back,
bottomBar = { Bar(cameraUiState.pageCount, onFinalizePressed, onImportClicked) }
) { modifier ->
if (!isCameraPermissionGranted) {
if (cameraUiState.importState is ImportState.Selecting) {
// display nothing: photo picker is active
} else if (cameraUiState.importState is ImportState.Importing) {
ImportInProgress(cameraUiState.importState, modifier)
} else if (!isCameraPermissionGranted) {
CameraPermissionRationale(onRequestCameraPermission, modifier)
} else {
CameraPreviewBox(
@@ -312,6 +322,43 @@ private fun CameraScreenScaffold(
}
}
@Composable
fun ImportInProgress(state: ImportState.Importing, modifier: Modifier) {
Box(
modifier = modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.6f)),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.fillMaxWidth().padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = LocalResources.current.getQuantityString(
R.plurals.importing_photos,
state.total,
state.total
),
color = Color.White
)
Spacer(Modifier.height(16.dp))
if (state.total > 1) {
LinearProgressIndicator(
progress = { state.processed.toFloat() / state.total },
modifier = Modifier.fillMaxWidth()
)
} else {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
@Composable
private fun CameraPreviewBox(
cameraPreview: @Composable (() -> Unit),
@@ -540,10 +587,11 @@ private fun Bar(
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary)
) {
Icon(
Icons.Default.AddPhotoAlternate,
// TODO Externalize string
contentDescription = "Import photos",)
Icon(Icons.Default.AddPhotoAlternate, contentDescription = null)
Spacer(Modifier.width(4.dp))
Text(stringResource(R.string.import_photos),
maxLines = 1,
overflow = TextOverflow.Ellipsis)
}
MainActionButton(
onClick = onFinalizePressed,
@@ -613,9 +661,16 @@ fun CameraScreenPreviewWithNoPermission() {
ScreenPreview(CaptureState.Idle, isCameraPermissionGranted = false)
}
@Preview
@Composable
fun CameraScreenPreviewImporting() {
ScreenPreview(CaptureState.Idle, ImportState.Importing(2, 3))
}
@Composable
private fun ScreenPreview(
captureState: CaptureState,
importState: ImportState = ImportState.Idle,
rotationDegrees: Float = 0f,
isCameraPermissionGranted: Boolean = true,
) {
@@ -649,7 +704,7 @@ private fun ScreenPreview(
showPageNumbers = false,
),
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState,
false, rotationDegrees > 0, false, false),
importState, false, rotationDegrees > 0, false, false),
onCapture = {},
onFinalizePressed = {},
onDebugModeSwitched = {},

View File

@@ -27,10 +27,17 @@ data class LiveAnalysisState(
val stableQuad: Quad? = null,
)
sealed class ImportState {
object Idle : ImportState()
object Selecting : ImportState()
data class Importing(val processed: Int, val total: Int) : ImportState()
}
data class CameraUiState(
val pageCount: Int,
val liveAnalysisState: LiveAnalysisState,
val captureState: CaptureState,
val importState: ImportState,
val showDetectionError: Boolean,
val isLandscape: Boolean,
val isDebugMode: Boolean,

View File

@@ -56,6 +56,9 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
private val _captureState = MutableStateFlow<CaptureState>(CaptureState.Idle)
val captureState: StateFlow<CaptureState> = _captureState
private val _importState = MutableStateFlow<ImportState>(ImportState.Idle)
val importState: StateFlow<ImportState> = _importState
private val _isTorchEnabled = MutableStateFlow(false)
val isTorchEnabled: StateFlow<Boolean> = _isTorchEnabled
@@ -87,7 +90,7 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
}
fun liveAnalysis(imageProxy: ImageProxy) {
if (_captureState.value !is CaptureState.Idle) {
if (_captureState.value !is CaptureState.Idle || _importState.value !is ImportState.Idle) {
imageProxy.close()
return
}
@@ -188,16 +191,28 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
}
fun importPhotos(uris: List<Uri>) {
if (uris.isEmpty()) {
_importState.value = ImportState.Idle
return
}
viewModelScope.launch {
for (uri in uris) {
_importState.value = ImportState.Importing(0, uris.size)
uris.forEachIndexed { index, uri ->
val photoToImport = imageLoader.load(uri)
val page = processCapturedImage(photoToImport, 0)
page?.let {
_events.emit(CameraEvent.ImageCaptured(it))
}
_importState.value = ImportState.Importing(index + 1, uris.size)
}
_importState.value = ImportState.Idle
}
}
fun onImportClicked() {
_importState.value = ImportState.Selecting
resetLiveAnalysis()
}
}
sealed class CaptureState {

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">الحجم الكلي: %1$s</string>
<string name="filename">اسم الملف</string>
<string name="grant_permission">امنح الأذن</string>
<string name="import_photos">استيراد</string>
<string name="last_saved_pdf_files">ملفات PDF المحفوظة حديثًا على هذا الجهاز:</string>
<string name="libraries">المكتبات</string>
<string name="libraries_open_source">مكتبات مفتوحة المصدر</string>
@@ -82,6 +83,14 @@
<item quantity="many">حُفظ %1$d ملفًا في %3$s</item>
<item quantity="other">حُفظ %1$d ملف في %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="zero">جارٍ استيراد 0 صورة</item>
<item quantity="one">جارٍ استيراد صورة واحدة</item>
<item quantity="two">جارٍ استيراد صورتين</item>
<item quantity="few">جارٍ استيراد %d صور</item>
<item quantity="many">جارٍ استيراد %d صورة</item>
<item quantity="other">جارٍ استيراد %d صورة</item>
</plurals>
<plurals name="page_count">
<item quantity="zero">لا صفحات</item>
<item quantity="one">%d صفحة</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Celková velikost: %1$s</string>
<string name="filename">Název souboru</string>
<string name="grant_permission">Povolit přístup</string>
<string name="import_photos">Import</string>
<string name="last_saved_pdf_files">Poslední PDF uložené v tomto zařízení:</string>
<string name="libraries">Kníhovny</string>
<string name="libraries_open_source">Open-source knihovny</string>
@@ -80,6 +81,12 @@
<item quantity="many">%1$d souborů uloženo do %3$s</item>
<item quantity="other">%1$d souborů uloženo do %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">Importování 1 fotografie</item>
<item quantity="few">Importování %d fotografií</item>
<item quantity="many">Importování %d fotografií</item>
<item quantity="other">Importování %d fotografií</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d stránka</item> <!-- 1 -->
<item quantity="few">%d stránky</item> <!-- 24 -->

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Gesamtgröße: %1$s</string>
<string name="filename">Dateiname</string>
<string name="grant_permission">Berechtigung erteilen</string>
<string name="import_photos">Importieren</string>
<string name="last_saved_pdf_files">Zuletzt auf diesem Gerät gespeicherte PDFs:</string>
<string name="libraries">Bibliotheken</string>
<string name="libraries_open_source">Open-Source-Bibliotheken</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s gespeichert in %3$s</item>
<item quantity="other">%1$d Dateien gespeichert in %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">1 Foto wird importiert</item>
<item quantity="other">%d Fotos werden importiert</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d Seite</item>
<item quantity="other">%d Seiten</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Tamaño total: %1$s</string>
<string name="filename">Nombre del archivo</string>
<string name="grant_permission">Conceder permiso</string>
<string name="import_photos">Importar</string>
<string name="last_saved_pdf_files">PDF recientes guardados en este dispositivo:</string>
<string name="libraries">Bibliotecas</string>
<string name="libraries_open_source">Bibliotecas de código abierto</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s guardado en %3$s</item>
<item quantity="other">%1$d archivos guardados en %3$s</item>
</plurals>
<plurals name="importing_photos" tools:ignore="MissingQuantity">
<item quantity="one">Importando 1 foto</item>
<item quantity="other">Importando %d fotos</item>
</plurals>
<plurals name="page_count" tools:ignore="MissingQuantity">
<item quantity="one">%d página</item>
<item quantity="other">%d páginas</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Taille totale : %1$s</string>
<string name="filename">Nom de fichier</string>
<string name="grant_permission">Autoriser</string>
<string name="import_photos">Importer</string>
<string name="last_saved_pdf_files">Derniers PDF enregistrés sur lappareil :</string>
<string name="libraries">Bibliothèques</string>
<string name="libraries_open_source">Bibliothèques open source</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s enregistré dans %3$s</item>
<item quantity="other">%1$d fichiers enregistrés dans %3$s</item>
</plurals>
<plurals name="importing_photos" tools:ignore="MissingQuantity">
<item quantity="one">Import de %d photo</item>
<item quantity="other">Import de %d photos</item>
</plurals>
<plurals name="page_count" tools:ignore="MissingQuantity">
<item quantity="one">%d page</item>
<item quantity="other">%d pages</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Tamaño total: %1$s</string>
<string name="filename">Nome do ficheiro</string>
<string name="grant_permission">Conceder permiso</string>
<string name="import_photos">Importar</string>
<string name="last_saved_pdf_files">PDF recentes gardados neste dispositivo:</string>
<string name="libraries">Bibliotecas</string>
<string name="libraries_open_source">Bibliotecas de código aberto</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s gardado en %3$s</item>
<item quantity="other">%1$d ficheiros gardados en %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">Importando 1 foto</item>
<item quantity="other">Importando %d fotos</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d páxina</item>
<item quantity="other">%d páxinas</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Dimensione totale: %1$s</string>
<string name="filename">Nome file</string>
<string name="grant_permission">Concendi autorizzazione</string>
<string name="import_photos">Importa</string>
<string name="last_saved_pdf_files">PDF recenti salvati su questo dispositivo:</string>
<string name="libraries">Librerie</string>
<string name="libraries_open_source">Librerie open source</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s salvato in %3$s</item>
<item quantity="other">%1$d file salvati in %3$s</item>
</plurals>
<plurals name="importing_photos" tools:ignore="MissingQuantity">
<item quantity="one">Importazione di 1 foto</item>
<item quantity="other">Importazione di %d foto</item>
</plurals>
<plurals name="page_count" tools:ignore="MissingQuantity">
<item quantity="one">%d pagina</item>
<item quantity="other">%d pagine</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Tamanho total: %1$s</string>
<string name="filename">Nome do arquivo</string>
<string name="grant_permission">Conceder permissão</string>
<string name="import_photos">Importar</string>
<string name="last_saved_pdf_files">PDFs recentes salvos neste dispositivo:</string>
<string name="libraries">Bibliotecas</string>
<string name="libraries_open_source">Bibliotecas de código aberto</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s salvo em %3$s</item>
<item quantity="other">%1$d arquivos salvos em %3$s</item>
</plurals>
<plurals name="importing_photos" tools:ignore="MissingQuantity">
<item quantity="one">Importando %d foto</item>
<item quantity="other">Importando %d fotos</item>
</plurals>
<plurals name="page_count" tools:ignore="MissingQuantity">
<item quantity="one">%d página</item>
<item quantity="other">%d páginas</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Общий размер: %1$s</string>
<string name="filename">Имя файла</string>
<string name="grant_permission">Предоставить разрешение</string>
<string name="import_photos">Импорт</string>
<string name="last_saved_pdf_files">Последние PDF, сохранённые на этом устройстве:</string>
<string name="libraries">Библиотеки</string>
<string name="libraries_open_source">Библиотеки с открытым исходным кодом</string>
@@ -80,6 +81,12 @@
<item quantity="many">%1$d файлов сохранено в %3$s</item>
<item quantity="other">%1$d файла сохранено в %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">Импорт %d фото</item>
<item quantity="few">Импорт %d фото</item>
<item quantity="many">Импорт %d фото</item>
<item quantity="other">Импорт %d фото</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d страница</item>
<item quantity="few">%d страницы</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">Toplam boyut: %1$s</string>
<string name="filename">Dosya adı</string>
<string name="grant_permission">İzin ver</string>
<string name="import_photos">İçe aktar</string>
<string name="last_saved_pdf_files">Bu cihaza kaydedilen son PDF\'ler:</string>
<string name="libraries">Kütüphaneler</string>
<string name="libraries_open_source">ık kaynaklı kütüphaneler</string>
@@ -78,6 +79,10 @@
<item quantity="one">%2$s, %3$s konumuna kaydedildi</item>
<item quantity="other">%1$d dosya %3$s konumuna kaydedildi</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">1 foto içe aktarılıyor</item>
<item quantity="other">%d foto içe aktarılıyor</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d sayfa</item>
<item quantity="other">%d sayfa</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">總計大小:%1$s</string>
<string name="filename">檔案名稱</string>
<string name="grant_permission">授予權限</string>
<string name="import_photos">匯入</string>
<string name="last_saved_pdf_files">此裝置上最近儲存的 PDF</string>
<string name="libraries">函式庫</string>
<string name="libraries_open_source">開放原始碼函式庫</string>
@@ -78,6 +79,9 @@
<item quantity="one">%2$s 已儲存至 %3$s</item>
<item quantity="other">%1$d 個檔案已儲存至 %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="other">正在匯入 %d 張照片</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d 頁</item>
<item quantity="other">%d 頁</item>

View File

@@ -43,6 +43,7 @@
<string name="file_size_total">总大小:%1$s</string>
<string name="filename">文件名字</string>
<string name="grant_permission">授予权限</string>
<string name="import_photos">导入</string>
<string name="last_saved_pdf_files">最近保存在此设备上的 PDF</string>
<string name="libraries"></string>
<string name="libraries_open_source">开源库</string>
@@ -77,6 +78,9 @@
<plurals name="files_saved_to">
<item quantity="other">%1$d 个文件已保存到 %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="other">正在导入 %d 张照片</item>
</plurals>
<plurals name="page_count">
<item quantity="other">%d 页</item>
</plurals>

View File

@@ -47,6 +47,7 @@
<string name="file_size_total">Total size: %1$s</string>
<string name="filename">Filename</string>
<string name="grant_permission">Grant permission</string>
<string name="import_photos">Import</string>
<string name="last_saved_pdf_files">Recent PDFs saved on this device:</string>
<string name="libraries">Libraries</string>
<string name="libraries_open_source">Open-source libraries</string>
@@ -82,6 +83,10 @@
<item quantity="one">%2$s saved to %3$s</item>
<item quantity="other">%1$d files saved to %3$s</item>
</plurals>
<plurals name="importing_photos">
<item quantity="one">Importing 1 photo</item>
<item quantity="other">Importing %d photos</item>
</plurals>
<plurals name="page_count">
<item quantity="one">%d page</item>
<item quantity="other">%d pages</item>