From 6290717635c219aebeddf58781d59f0424bdcc43 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:01:36 +0200 Subject: [PATCH] CameraScreen: improve UI for import --- .../java/org/fairscan/app/MainActivity.kt | 7 +- .../app/ui/screens/camera/CameraScreen.kt | 67 +++++++++++++++++-- .../app/ui/screens/camera/CameraUiState.kt | 7 ++ .../app/ui/screens/camera/CameraViewModel.kt | 19 +++++- app/src/main/res/values-ar/strings.xml | 9 +++ app/src/main/res/values-cs/strings.xml | 7 ++ app/src/main/res/values-de/strings.xml | 5 ++ app/src/main/res/values-es/strings.xml | 5 ++ app/src/main/res/values-fr/strings.xml | 5 ++ app/src/main/res/values-gl/strings.xml | 5 ++ app/src/main/res/values-it/strings.xml | 5 ++ app/src/main/res/values-pt-rBR/strings.xml | 5 ++ app/src/main/res/values-ru/strings.xml | 7 ++ app/src/main/res/values-tr/strings.xml | 5 ++ app/src/main/res/values-zh-rTW/strings.xml | 4 ++ app/src/main/res/values-zh/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 5 ++ 17 files changed, 161 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/fairscan/app/MainActivity.kt b/app/src/main/java/org/fairscan/app/MainActivity.kt index 551f30a..d63c9c7 100644 --- a/app/src/main/java/org/fairscan/app/MainActivity.kt +++ b/app/src/main/java/org/fairscan/app/MainActivity.kt @@ -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)) } diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt index 062575a..bc66037 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt @@ -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 = {}, diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraUiState.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraUiState.kt index 79f05f0..2a12b24 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraUiState.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraUiState.kt @@ -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, diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt index 0180d4c..4cd6dd4 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt @@ -56,6 +56,9 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() { private val _captureState = MutableStateFlow(CaptureState.Idle) val captureState: StateFlow = _captureState + private val _importState = MutableStateFlow(ImportState.Idle) + val importState: StateFlow = _importState + private val _isTorchEnabled = MutableStateFlow(false) val isTorchEnabled: StateFlow = _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) { + 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 { diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index e41eb68..1da27a2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -43,6 +43,7 @@ الحجم الكلي: %1$s اسم الملف امنح الأذن + استيراد ملفات PDF المحفوظة حديثًا على هذا الجهاز: المكتبات مكتبات مفتوحة المصدر @@ -82,6 +83,14 @@ حُفظ %1$d ملفًا في %3$s حُفظ %1$d ملف في %3$s + + جارٍ استيراد 0 صورة + جارٍ استيراد صورة واحدة + جارٍ استيراد صورتين + جارٍ استيراد %d صور + جارٍ استيراد %d صورة + جارٍ استيراد %d صورة + لا صفحات %d صفحة diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b3646ff..644127e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -43,6 +43,7 @@ Celková velikost: %1$s Název souboru Povolit přístup + Import Poslední PDF uložené v tomto zařízení: Kníhovny Open-source knihovny @@ -80,6 +81,12 @@ %1$d souborů uloženo do %3$s %1$d souborů uloženo do %3$s + + Importování 1 fotografie + Importování %d fotografií + Importování %d fotografií + Importování %d fotografií + %d stránka %d stránky diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1a38c5e..ff40837 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -43,6 +43,7 @@ Gesamtgröße: %1$s Dateiname Berechtigung erteilen + Importieren Zuletzt auf diesem Gerät gespeicherte PDFs: Bibliotheken Open-Source-Bibliotheken @@ -78,6 +79,10 @@ %2$s gespeichert in %3$s %1$d Dateien gespeichert in %3$s + + 1 Foto wird importiert + %d Fotos werden importiert + %d Seite %d Seiten diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index bf0940d..33c661b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -43,6 +43,7 @@ Tamaño total: %1$s Nombre del archivo Conceder permiso + Importar PDF recientes guardados en este dispositivo: Bibliotecas Bibliotecas de código abierto @@ -78,6 +79,10 @@ %2$s guardado en %3$s %1$d archivos guardados en %3$s + + Importando 1 foto + Importando %d fotos + %d página %d páginas diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5730659..520e514 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -43,6 +43,7 @@ Taille totale : %1$s Nom de fichier Autoriser + Importer Derniers PDF enregistrés sur l’appareil : Bibliothèques Bibliothèques open source @@ -78,6 +79,10 @@ %2$s enregistré dans %3$s %1$d fichiers enregistrés dans %3$s + + Import de %d photo + Import de %d photos + %d page %d pages diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 34ba6f5..38a4926 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -43,6 +43,7 @@ Tamaño total: %1$s Nome do ficheiro Conceder permiso + Importar PDF recentes gardados neste dispositivo: Bibliotecas Bibliotecas de código aberto @@ -78,6 +79,10 @@ %2$s gardado en %3$s %1$d ficheiros gardados en %3$s + + Importando 1 foto + Importando %d fotos + %d páxina %d páxinas diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 98ad5d5..7bc9ed0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -43,6 +43,7 @@ Dimensione totale: %1$s Nome file Concendi autorizzazione + Importa PDF recenti salvati su questo dispositivo: Librerie Librerie open source @@ -78,6 +79,10 @@ %2$s salvato in %3$s %1$d file salvati in %3$s + + Importazione di 1 foto + Importazione di %d foto + %d pagina %d pagine diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6b47479..23c16d0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -43,6 +43,7 @@ Tamanho total: %1$s Nome do arquivo Conceder permissão + Importar PDFs recentes salvos neste dispositivo: Bibliotecas Bibliotecas de código aberto @@ -78,6 +79,10 @@ %2$s salvo em %3$s %1$d arquivos salvos em %3$s + + Importando %d foto + Importando %d fotos + %d página %d páginas diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5483b21..ad10ec8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -43,6 +43,7 @@ Общий размер: %1$s Имя файла Предоставить разрешение + Импорт Последние PDF, сохранённые на этом устройстве: Библиотеки Библиотеки с открытым исходным кодом @@ -80,6 +81,12 @@ %1$d файлов сохранено в %3$s %1$d файла сохранено в %3$s + + Импорт %d фото + Импорт %d фото + Импорт %d фото + Импорт %d фото + %d страница %d страницы diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1928d68..64faf48 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -43,6 +43,7 @@ Toplam boyut: %1$s Dosya adı İzin ver + İçe aktar Bu cihaza kaydedilen son PDF\'ler: Kütüphaneler Açık kaynaklı kütüphaneler @@ -78,6 +79,10 @@ %2$s, %3$s konumuna kaydedildi %1$d dosya %3$s konumuna kaydedildi + + 1 foto içe aktarılıyor + %d foto içe aktarılıyor + %d sayfa %d sayfa diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 547545c..265b31e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -43,6 +43,7 @@ 總計大小:%1$s 檔案名稱 授予權限 + 匯入 此裝置上最近儲存的 PDF: 函式庫 開放原始碼函式庫 @@ -78,6 +79,9 @@ %2$s 已儲存至 %3$s %1$d 個檔案已儲存至 %3$s + + 正在匯入 %d 張照片 + %d 頁 %d 頁 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index d594c6b..0cfe2d2 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -43,6 +43,7 @@ 总大小:%1$s 文件名字 授予权限 + 导入 最近保存在此设备上的 PDF: 开源库 @@ -77,6 +78,9 @@ %1$d 个文件已保存到 %3$s + + 正在导入 %d 张照片 + %d 页 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index da0d39d..d1e56bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ Total size: %1$s Filename Grant permission + Import Recent PDFs saved on this device: Libraries Open-source libraries @@ -82,6 +83,10 @@ %2$s saved to %3$s %1$d files saved to %3$s + + Importing 1 photo + Importing %d photos + %d page %d pages