add document scanner
This commit is contained in:
@@ -2,11 +2,13 @@ package com.vaultdrop.mobile.ui.scan
|
|||||||
|
|
||||||
import android.Manifest.permission.CAMERA
|
import android.Manifest.permission.CAMERA
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Scaffold
|
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).
|
// Retour arrière contextuel (géré par BackHandler dans le content).
|
||||||
val title = when (stage) {
|
val title = when (stage) {
|
||||||
ScanStage.CAMERA -> stringResource(R.string.scan_title)
|
ScanStage.CAMERA -> stringResource(R.string.scan_title)
|
||||||
@@ -94,7 +126,7 @@ fun ScanFlowScreen(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
when (stage) {
|
when (stage) {
|
||||||
ScanStage.CAMERA -> { viewModel.abandon(); onBack() }
|
ScanStage.CAMERA -> requestExit()
|
||||||
ScanStage.CROP -> viewModel.retake()
|
ScanStage.CROP -> viewModel.retake()
|
||||||
ScanStage.SESSION -> viewModel.openCamera()
|
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 com.vaultdrop.mobile.features.scan.ScanRenderMode
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -96,6 +98,13 @@ class ScanViewModel @Inject constructor(
|
|||||||
private val _pages = MutableStateFlow<List<ScanPageEntity>>(emptyList())
|
private val _pages = MutableStateFlow<List<ScanPageEntity>>(emptyList())
|
||||||
val pages: StateFlow<List<ScanPageEntity>> = _pages.asStateFlow()
|
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 {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val rootId = defaultRootStore.get()
|
val rootId = defaultRootStore.get()
|
||||||
@@ -303,7 +312,7 @@ class ScanViewModel @Inject constructor(
|
|||||||
/** Annule la session : suppression des fichiers temp + fermeture. */
|
/** Annule la session : suppression des fichiers temp + fermeture. */
|
||||||
fun abandon() {
|
fun abandon() {
|
||||||
val currentSession = _session.value ?: return
|
val currentSession = _session.value ?: return
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
cleanupScope.launch {
|
||||||
scanRepository.getPages(currentSession.id).forEach { File(it.tempUri).delete() }
|
scanRepository.getPages(currentSession.id).forEach { File(it.tempUri).delete() }
|
||||||
sessionDir(currentSession.id)?.delete()
|
sessionDir(currentSession.id)?.delete()
|
||||||
_pages.value = emptyList()
|
_pages.value = emptyList()
|
||||||
|
|||||||
Reference in New Issue
Block a user