add document scanner

This commit is contained in:
m
2026-09-13 22:05:13 +02:00
parent 612e3b0eff
commit 8a4cd44ef8
2 changed files with 60 additions and 2 deletions
@@ -2,11 +2,13 @@ package com.vaultdrop.mobile.ui.scan
import android.Manifest.permission.CAMERA
import android.content.pm.PackageManager
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
@@ -80,6 +82,36 @@ fun ScanFlowScreen(
}
}
// Confirmation avant de quitter le scanner quand des pages sont en cours :
// la flèche CAMERA et le back système feront tous deux valider par un
// dialogue (sinon abandon = suppression des pages capturées).
var showExitConfirm by remember { mutableStateOf(false) }
val requestExit: () -> Unit = {
if (pages.isNotEmpty()) {
showExitConfirm = true
} else {
viewModel.abandon()
onBack()
}
}
val confirmExit: () -> Unit = {
showExitConfirm = false
viewModel.abandon()
onBack()
}
// Retour système : étage courant (CROP/SESSION) → reprise dans le scanner ;
// seul CAMERA quitte l'écran (avec confirmation si pages présentes).
BackHandler {
when (stage) {
ScanStage.CAMERA -> requestExit()
ScanStage.CROP -> viewModel.retake()
ScanStage.SESSION -> viewModel.openCamera()
}
}
// Retour arrière contextuel (géré par BackHandler dans le content).
val title = when (stage) {
ScanStage.CAMERA -> stringResource(R.string.scan_title)
@@ -94,7 +126,7 @@ fun ScanFlowScreen(
navigationIcon = {
IconButton(onClick = {
when (stage) {
ScanStage.CAMERA -> { viewModel.abandon(); onBack() }
ScanStage.CAMERA -> requestExit()
ScanStage.CROP -> viewModel.retake()
ScanStage.SESSION -> viewModel.openCamera()
}
@@ -145,6 +177,23 @@ fun ScanFlowScreen(
)
}
}
if (showExitConfirm) {
AlertDialog(
onDismissRequest = { showExitConfirm = false },
title = { Text(stringResource(R.string.scan_abandon_title)) },
text = { Text(stringResource(R.string.scan_abandon_message)) },
confirmButton = {
TextButton(onClick = confirmExit) {
Text(stringResource(R.string.scan_confirm))
}
},
dismissButton = {
TextButton(onClick = { showExitConfirm = false }) {
Text(stringResource(R.string.scan_cancel))
}
},
)
}
}
}
@@ -26,7 +26,9 @@ import com.vaultdrop.mobile.features.scan.ScanQuad
import com.vaultdrop.mobile.features.scan.ScanRenderMode
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -96,6 +98,13 @@ class ScanViewModel @Inject constructor(
private val _pages = MutableStateFlow<List<ScanPageEntity>>(emptyList())
val pages: StateFlow<List<ScanPageEntity>> = _pages.asStateFlow()
/**
* Scope de nettoyage indépendant du ViewModel : la suppression des fichiers
* d'une session abandonnée est lancée puis on quitte l'écran, ce qui détruit
* le ViewModel — `viewModelScope` serait annulé en plein nettoyage.
*/
private val cleanupScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
init {
viewModelScope.launch {
val rootId = defaultRootStore.get()
@@ -303,7 +312,7 @@ class ScanViewModel @Inject constructor(
/** Annule la session : suppression des fichiers temp + fermeture. */
fun abandon() {
val currentSession = _session.value ?: return
viewModelScope.launch(Dispatchers.IO) {
cleanupScope.launch {
scanRepository.getPages(currentSession.id).forEach { File(it.tempUri).delete() }
sessionDir(currentSession.id)?.delete()
_pages.value = emptyList()