fix scanner + pdf creator
This commit is contained in:
+87
-26
@@ -17,9 +17,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.CreateNewFolder
|
||||
import androidx.compose.material.icons.filled.DriveFileMove
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.MergeType
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
@@ -60,6 +58,12 @@ import com.vaultdrop.mobile.ui.components.SelectionStatusIcon
|
||||
import com.vaultdrop.mobile.ui.components.ServerStatusBadge
|
||||
import com.vaultdrop.mobile.ui.components.SyncStatusAction
|
||||
import com.vaultdrop.mobile.ui.components.rememberSelectionState
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import com.vaultdrop.mobile.ui.components.DeleteConfirmDialog
|
||||
import com.vaultdrop.mobile.ui.components.DeleteReview
|
||||
import com.vaultdrop.mobile.ui.components.DeleteWarningDialog
|
||||
import com.vaultdrop.mobile.ui.components.reviewDelete
|
||||
import com.vaultdrop.mobile.ui.navigation.SelectionNavBar
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -79,7 +83,12 @@ fun FolderDetailScreen(
|
||||
val selection = rememberSelectionState()
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var showMoveDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteWarning by remember { mutableStateOf(false) }
|
||||
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||
var pendingDeleteMode by remember { mutableStateOf<FileDeleter.DeleteMode?>(null) }
|
||||
var pendingDeleteReview by remember { mutableStateOf<DeleteReview?>(null) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val deletedLabel = stringResource(R.string.delete)
|
||||
|
||||
LaunchedEffect(uiState.folderMissing) {
|
||||
if (uiState.folderMissing) onBack()
|
||||
@@ -101,6 +110,21 @@ fun FolderDetailScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.deleteError) {
|
||||
val deleteError = uiState.deleteError
|
||||
if (deleteError != null) {
|
||||
snackbarHostState.showSnackbar(deleteError)
|
||||
viewModel.clearDeleteError()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.deleteSuccess) {
|
||||
if (uiState.deleteSuccess) {
|
||||
snackbarHostState.showSnackbar(deletedLabel)
|
||||
viewModel.clearDeleteSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showMoveDialog) {
|
||||
if (showMoveDialog) viewModel.loadMoveFolders()
|
||||
}
|
||||
@@ -135,30 +159,7 @@ fun FolderDetailScreen(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (selection.active) {
|
||||
IconButton(
|
||||
onClick = { showMoveDialog = true },
|
||||
enabled = selection.ids.isNotEmpty(),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.DriveFileMove,
|
||||
contentDescription = stringResource(R.string.move_files),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
val ids = selection.ids.toList()
|
||||
selection.clear()
|
||||
onBuildPdf(ids)
|
||||
},
|
||||
enabled = selection.ids.isNotEmpty(),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.MergeType,
|
||||
contentDescription = stringResource(R.string.selection_assemble),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!selection.active) {
|
||||
IconButton(onClick = { showCreateDialog = true }) {
|
||||
Icon(
|
||||
Icons.Filled.CreateNewFolder,
|
||||
@@ -178,6 +179,30 @@ fun FolderDetailScreen(
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
bottomBar = {
|
||||
if (selection.active) {
|
||||
SelectionNavBar(
|
||||
onMove = { showMoveDialog = true },
|
||||
onBuildPdf = {
|
||||
val ids = selection.ids.toList()
|
||||
selection.clear()
|
||||
onBuildPdf(ids)
|
||||
},
|
||||
onDeleteModeSelected = { mode ->
|
||||
val files = uiState.files.filter { it.resourceId in selection.ids }
|
||||
val review = reviewDelete(files, mode)
|
||||
pendingDeleteMode = mode
|
||||
pendingDeleteReview = review
|
||||
if (review.hasWarning) {
|
||||
showDeleteWarning = true
|
||||
} else {
|
||||
showDeleteConfirm = true
|
||||
}
|
||||
},
|
||||
enabled = selection.ids.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
FolderDetailContent(
|
||||
subFolders = uiState.subFolders,
|
||||
@@ -218,6 +243,42 @@ fun FolderDetailScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val deleteReview = pendingDeleteReview
|
||||
if (showDeleteWarning && deleteReview != null) {
|
||||
DeleteWarningDialog(
|
||||
review = deleteReview,
|
||||
onContinue = {
|
||||
showDeleteWarning = false
|
||||
showDeleteConfirm = true
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteWarning = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDeleteConfirm && pendingDeleteMode != null) {
|
||||
DeleteConfirmDialog(
|
||||
count = selection.ids.size,
|
||||
onConfirm = {
|
||||
val ids = selection.ids.toList()
|
||||
val mode = pendingDeleteMode!!
|
||||
selection.clear()
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
viewModel.deleteSelectedFiles(ids, mode)
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+31
@@ -11,6 +11,7 @@ import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
||||
import com.vaultdrop.mobile.data.repository.FileRepository
|
||||
import com.vaultdrop.mobile.data.repository.FolderRepository
|
||||
import com.vaultdrop.mobile.data.repository.SaveFolderInput
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import com.vaultdrop.mobile.features.saf.FileMover
|
||||
import com.vaultdrop.mobile.features.saf.SafFolderCreator
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -35,6 +36,10 @@ data class FolderDetailUiState(
|
||||
val moveFolders: List<FolderEntity>? = null,
|
||||
/** Erreur transitoire de déplacement — affichée en Snackbar puis effacée. */
|
||||
val moveError: String? = null,
|
||||
/** Erreur transitoire de suppression — affichée en Snackbar puis effacée. */
|
||||
val deleteError: String? = null,
|
||||
/** Succès transitoire de suppression — affiché en Snackbar puis effacé. */
|
||||
val deleteSuccess: Boolean = false,
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@@ -45,6 +50,7 @@ class FolderDetailViewModel @Inject constructor(
|
||||
private val tokenProvider: TokenProvider,
|
||||
private val safFolderCreator: SafFolderCreator,
|
||||
private val fileMover: FileMover,
|
||||
private val fileDeleter: FileDeleter,
|
||||
@ApplicationContext private val context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -165,4 +171,29 @@ class FolderDetailViewModel @Inject constructor(
|
||||
fun clearMoveError() {
|
||||
_uiState.update { it.copy(moveError = null) }
|
||||
}
|
||||
|
||||
/** Supprime les fichiers sélectionnés selon le mode choisi. */
|
||||
fun deleteSelectedFiles(resourceIds: List<String>, mode: FileDeleter.DeleteMode) {
|
||||
viewModelScope.launch {
|
||||
val files = resourceIds.mapNotNull { fileRepository.getFile(it) }
|
||||
if (files.isEmpty()) {
|
||||
_uiState.update { it.copy(deleteError = context.getString(R.string.delete_error)) }
|
||||
return@launch
|
||||
}
|
||||
val report = fileDeleter.deleteFiles(files, mode)
|
||||
if (report.failed > 0) {
|
||||
_uiState.update { it.copy(deleteError = context.getString(R.string.delete_error)) }
|
||||
} else {
|
||||
_uiState.update { it.copy(deleteSuccess = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDeleteError() {
|
||||
_uiState.update { it.copy(deleteError = null) }
|
||||
}
|
||||
|
||||
fun clearDeleteSuccess() {
|
||||
_uiState.update { it.copy(deleteSuccess = false) }
|
||||
}
|
||||
}
|
||||
+74
@@ -72,6 +72,10 @@ import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
||||
import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel
|
||||
import com.vaultdrop.mobile.features.saf.safDisplayName
|
||||
import com.vaultdrop.mobile.features.sync.SyncViewModel
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import com.vaultdrop.mobile.ui.components.DeleteConfirmDialog
|
||||
import com.vaultdrop.mobile.ui.components.DeleteReview
|
||||
import com.vaultdrop.mobile.ui.components.DeleteWarningDialog
|
||||
import com.vaultdrop.mobile.ui.components.FileCategoryIcon
|
||||
import com.vaultdrop.mobile.ui.components.FileSyncStatusIcon
|
||||
import com.vaultdrop.mobile.ui.components.FolderNameDialog
|
||||
@@ -80,6 +84,7 @@ import com.vaultdrop.mobile.ui.components.SelectionStatusIcon
|
||||
import com.vaultdrop.mobile.ui.components.ServerStatusBadge
|
||||
import com.vaultdrop.mobile.ui.components.SyncStatusAction
|
||||
import com.vaultdrop.mobile.ui.components.rememberSelectionState
|
||||
import com.vaultdrop.mobile.ui.components.reviewDelete
|
||||
import com.vaultdrop.mobile.ui.navigation.FloatingNavBar
|
||||
import com.vaultdrop.mobile.ui.navigation.MoveTargetBar
|
||||
import com.vaultdrop.mobile.ui.navigation.NavTab
|
||||
@@ -106,6 +111,10 @@ fun FolderListScreen(
|
||||
val context = LocalContext.current
|
||||
val selection = rememberSelectionState()
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteWarning by remember { mutableStateOf(false) }
|
||||
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||
var pendingDeleteMode by remember { mutableStateOf<FileDeleter.DeleteMode?>(null) }
|
||||
var pendingDeleteReview by remember { mutableStateOf<DeleteReview?>(null) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val createError = uiState.createError
|
||||
@@ -131,6 +140,21 @@ fun FolderListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.deleteError) {
|
||||
val deleteError = uiState.deleteError
|
||||
if (deleteError != null) {
|
||||
snackbarHostState.showSnackbar(deleteError)
|
||||
viewModel.clearDeleteError()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.deleteSuccess) {
|
||||
if (uiState.deleteSuccess) {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.delete))
|
||||
viewModel.clearDeleteSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
// Racine par défaut : dossier VaultDrop choisi au premier lancement.
|
||||
val defaultRootLabel = stringResource(R.string.default_root_folder_label)
|
||||
var pendingDefaultPick by remember { mutableStateOf<Uri?>(null) }
|
||||
@@ -243,6 +267,20 @@ fun FolderListScreen(
|
||||
selection.clear()
|
||||
onBuildPdf(ids)
|
||||
},
|
||||
onDeleteModeSelected = { mode ->
|
||||
val allFiles = uiState.sections.flatMap { section ->
|
||||
section.rows.flatMap { row -> listOfNotNull(row.left, row.right) }
|
||||
}
|
||||
val selectedFiles = allFiles.filter { it.resourceId in selection.ids }
|
||||
val review = reviewDelete(selectedFiles, mode)
|
||||
pendingDeleteMode = mode
|
||||
pendingDeleteReview = review
|
||||
if (review.hasWarning) {
|
||||
showDeleteWarning = true
|
||||
} else {
|
||||
showDeleteConfirm = true
|
||||
}
|
||||
},
|
||||
enabled = selection.ids.isNotEmpty(),
|
||||
)
|
||||
else -> FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||
@@ -288,6 +326,42 @@ fun FolderListScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val deleteReview = pendingDeleteReview
|
||||
if (showDeleteWarning && deleteReview != null) {
|
||||
DeleteWarningDialog(
|
||||
review = deleteReview,
|
||||
onContinue = {
|
||||
showDeleteWarning = false
|
||||
showDeleteConfirm = true
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteWarning = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDeleteConfirm && pendingDeleteMode != null) {
|
||||
DeleteConfirmDialog(
|
||||
count = selection.ids.size,
|
||||
onConfirm = {
|
||||
val ids = selection.ids.toList()
|
||||
val mode = pendingDeleteMode!!
|
||||
selection.clear()
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
viewModel.deleteSelectedFiles(ids, mode)
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Sélecteur de layer de la page Fichiers — même habillage pill que la nav flottante. */
|
||||
|
||||
+4
@@ -46,4 +46,8 @@ data class FolderListUiState(
|
||||
val moveError: String? = null,
|
||||
/** Succès transitoire de déplacement — affiché en Snackbar puis effacé. */
|
||||
val moveSuccess: Boolean = false,
|
||||
/** Erreur transitoire de suppression — affichée en Snackbar puis effacée. */
|
||||
val deleteError: String? = null,
|
||||
/** Succès transitoire de suppression — affiché en Snackbar puis effacé. */
|
||||
val deleteSuccess: Boolean = false,
|
||||
)
|
||||
+27
@@ -15,6 +15,7 @@ import com.vaultdrop.mobile.data.repository.FileRepository
|
||||
import com.vaultdrop.mobile.data.repository.FolderRepository
|
||||
import com.vaultdrop.mobile.data.repository.SaveFolderInput
|
||||
import com.vaultdrop.mobile.features.saf.FileMover
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import com.vaultdrop.mobile.features.saf.SafFolderCreator
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -43,6 +44,7 @@ class FolderListViewModel @Inject constructor(
|
||||
private val defaultRootStore: DefaultRootStore,
|
||||
private val safFolderCreator: SafFolderCreator,
|
||||
private val fileMover: FileMover,
|
||||
private val fileDeleter: FileDeleter,
|
||||
@ApplicationContext private val context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -274,6 +276,31 @@ class FolderListViewModel @Inject constructor(
|
||||
_uiState.update { it.copy(moveError = null) }
|
||||
}
|
||||
|
||||
/** Supprime les fichiers sélectionnés selon le mode choisi. */
|
||||
fun deleteSelectedFiles(resourceIds: List<String>, mode: FileDeleter.DeleteMode) {
|
||||
viewModelScope.launch {
|
||||
val files = resourceIds.mapNotNull { fileRepository.getFile(it) }
|
||||
if (files.isEmpty()) {
|
||||
_uiState.update { it.copy(deleteError = context.getString(R.string.delete_error)) }
|
||||
return@launch
|
||||
}
|
||||
val report = fileDeleter.deleteFiles(files, mode)
|
||||
if (report.failed > 0) {
|
||||
_uiState.update { it.copy(deleteError = context.getString(R.string.delete_error)) }
|
||||
} else {
|
||||
_uiState.update { it.copy(deleteSuccess = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDeleteError() {
|
||||
_uiState.update { it.copy(deleteError = null) }
|
||||
}
|
||||
|
||||
fun clearDeleteSuccess() {
|
||||
_uiState.update { it.copy(deleteSuccess = false) }
|
||||
}
|
||||
|
||||
/** Grille d'accueil : tous les fichiers visibles, groupés par jour (date de référence). */
|
||||
private fun observeFiles() {
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -39,6 +39,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vaultdrop.mobile.R
|
||||
import com.vaultdrop.mobile.ui.components.DeleteDropdownButton
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
|
||||
enum class NavTab(val route: String) {
|
||||
FILES(Routes.FILES),
|
||||
@@ -133,6 +135,7 @@ fun FloatingNavBar(
|
||||
fun SelectionNavBar(
|
||||
onMove: () -> Unit,
|
||||
onBuildPdf: () -> Unit,
|
||||
onDeleteModeSelected: (FileDeleter.DeleteMode) -> Unit,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -167,6 +170,11 @@ fun SelectionNavBar(
|
||||
onClick = onBuildPdf,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
DeleteDropdownButton(
|
||||
enabled = enabled,
|
||||
onModeSelected = onDeleteModeSelected,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ import com.vaultdrop.mobile.data.local.entity.FileEntity
|
||||
import com.vaultdrop.mobile.domain.FileCategory
|
||||
import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel
|
||||
import com.vaultdrop.mobile.features.sync.SyncViewModel
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import com.vaultdrop.mobile.ui.components.DeleteConfirmDialog
|
||||
import com.vaultdrop.mobile.ui.components.DeleteReview
|
||||
import com.vaultdrop.mobile.ui.components.DeleteWarningDialog
|
||||
import com.vaultdrop.mobile.ui.components.FileCategoryIcon
|
||||
import com.vaultdrop.mobile.ui.components.FileSyncStatusIcon
|
||||
import com.vaultdrop.mobile.ui.components.MoveFolderPickerDialog
|
||||
@@ -67,6 +71,7 @@ import com.vaultdrop.mobile.ui.components.SyncStatusAction
|
||||
import com.vaultdrop.mobile.ui.components.color
|
||||
import com.vaultdrop.mobile.ui.components.icon
|
||||
import com.vaultdrop.mobile.ui.components.rememberSelectionState
|
||||
import com.vaultdrop.mobile.ui.components.reviewDelete
|
||||
import com.vaultdrop.mobile.ui.navigation.FloatingNavBar
|
||||
import com.vaultdrop.mobile.ui.navigation.NavTab
|
||||
import com.vaultdrop.mobile.ui.navigation.SelectionNavBar
|
||||
@@ -87,9 +92,16 @@ fun SearchScreen(
|
||||
val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle()
|
||||
val moveFolders by viewModel.moveFolders.collectAsStateWithLifecycle()
|
||||
val moveError by viewModel.moveError.collectAsStateWithLifecycle()
|
||||
val deleteError by viewModel.deleteError.collectAsStateWithLifecycle()
|
||||
val deleteSuccess by viewModel.deleteSuccess.collectAsStateWithLifecycle()
|
||||
val selection = rememberSelectionState()
|
||||
var showMoveDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteWarning by remember { mutableStateOf(false) }
|
||||
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||
var pendingDeleteMode by remember { mutableStateOf<FileDeleter.DeleteMode?>(null) }
|
||||
var pendingDeleteReview by remember { mutableStateOf<DeleteReview?>(null) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val deletedLabel = stringResource(R.string.delete)
|
||||
|
||||
LaunchedEffect(moveError) {
|
||||
val message = moveError
|
||||
@@ -99,6 +111,21 @@ fun SearchScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(deleteError) {
|
||||
val message = deleteError
|
||||
if (message != null) {
|
||||
snackbarHostState.showSnackbar(message)
|
||||
viewModel.clearDeleteError()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(deleteSuccess) {
|
||||
if (deleteSuccess) {
|
||||
snackbarHostState.showSnackbar(deletedLabel)
|
||||
viewModel.clearDeleteSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showMoveDialog) {
|
||||
if (showMoveDialog) viewModel.loadMoveFolders()
|
||||
}
|
||||
@@ -145,6 +172,17 @@ fun SearchScreen(
|
||||
selection.clear()
|
||||
onBuildPdf(ids)
|
||||
},
|
||||
onDeleteModeSelected = { mode ->
|
||||
val selectedFiles = uiState.results.filter { it.resourceId in selection.ids }
|
||||
val review = reviewDelete(selectedFiles, mode)
|
||||
pendingDeleteMode = mode
|
||||
pendingDeleteReview = review
|
||||
if (review.hasWarning) {
|
||||
showDeleteWarning = true
|
||||
} else {
|
||||
showDeleteConfirm = true
|
||||
}
|
||||
},
|
||||
enabled = selection.ids.isNotEmpty(),
|
||||
)
|
||||
} else {
|
||||
@@ -180,6 +218,42 @@ fun SearchScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val deleteReview = pendingDeleteReview
|
||||
if (showDeleteWarning && deleteReview != null) {
|
||||
DeleteWarningDialog(
|
||||
review = deleteReview,
|
||||
onContinue = {
|
||||
showDeleteWarning = false
|
||||
showDeleteConfirm = true
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteWarning = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDeleteConfirm && pendingDeleteMode != null) {
|
||||
DeleteConfirmDialog(
|
||||
count = selection.ids.size,
|
||||
onConfirm = {
|
||||
val ids = selection.ids.toList()
|
||||
val mode = pendingDeleteMode!!
|
||||
selection.clear()
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
viewModel.deleteSelectedFiles(ids, mode)
|
||||
},
|
||||
onDismiss = {
|
||||
showDeleteConfirm = false
|
||||
pendingDeleteMode = null
|
||||
pendingDeleteReview = null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
||||
import com.vaultdrop.mobile.data.repository.FileRepository
|
||||
import com.vaultdrop.mobile.data.repository.FolderRepository
|
||||
import com.vaultdrop.mobile.features.saf.FileMover
|
||||
import com.vaultdrop.mobile.features.saf.FileDeleter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -31,6 +32,7 @@ class SearchViewModel @Inject constructor(
|
||||
private val fileRepository: FileRepository,
|
||||
private val folderRepository: FolderRepository,
|
||||
private val fileMover: FileMover,
|
||||
private val fileDeleter: FileDeleter,
|
||||
@ApplicationContext private val context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -43,6 +45,12 @@ class SearchViewModel @Inject constructor(
|
||||
private val _moveError = MutableStateFlow<String?>(null)
|
||||
val moveError: StateFlow<String?> = _moveError.asStateFlow()
|
||||
|
||||
private val _deleteError = MutableStateFlow<String?>(null)
|
||||
val deleteError: StateFlow<String?> = _deleteError.asStateFlow()
|
||||
|
||||
private val _deleteSuccess = MutableStateFlow(false)
|
||||
val deleteSuccess: StateFlow<Boolean> = _deleteSuccess.asStateFlow()
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||
val uiState: StateFlow<SearchUiState> = combine(
|
||||
_searchQuery.debounce(SEARCH_DEBOUNCE_MS),
|
||||
@@ -96,6 +104,31 @@ class SearchViewModel @Inject constructor(
|
||||
_moveError.value = null
|
||||
}
|
||||
|
||||
/** Supprime les fichiers sélectionnés selon le mode choisi. */
|
||||
fun deleteSelectedFiles(resourceIds: List<String>, mode: FileDeleter.DeleteMode) {
|
||||
viewModelScope.launch {
|
||||
val files = resourceIds.mapNotNull { fileRepository.getFile(it) }
|
||||
if (files.isEmpty()) {
|
||||
_deleteError.value = context.getString(R.string.delete_error)
|
||||
return@launch
|
||||
}
|
||||
val report = fileDeleter.deleteFiles(files, mode)
|
||||
if (report.failed > 0) {
|
||||
_deleteError.value = context.getString(R.string.delete_error)
|
||||
} else {
|
||||
_deleteSuccess.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDeleteError() {
|
||||
_deleteError.value = null
|
||||
}
|
||||
|
||||
fun clearDeleteSuccess() {
|
||||
_deleteSuccess.value = false
|
||||
}
|
||||
|
||||
private data class CategoryQuery(val query: String, val category: String?)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -133,4 +133,18 @@
|
||||
<string name="pdf_builder_name_save">Save</string>
|
||||
<string name="pdf_builder_saved">PDF created</string>
|
||||
<string name="pdf_generated_folder">Generated PDFs</string>
|
||||
|
||||
<!-- Delete multi-select -->
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_locally">Delete locally</string>
|
||||
<string name="delete_in_cloud">Delete from cloud</string>
|
||||
<string name="delete_full">Delete everywhere</string>
|
||||
<string name="delete_confirm_title">Delete %1$d document(s)?</string>
|
||||
<string name="delete_confirm_message">This action cannot be undone.</string>
|
||||
<string name="delete_warning_title">Documents not synced</string>
|
||||
<string name="delete_warning_local_only_msg">%1$d out of %2$d document(s) are not synced to the cloud and will not be deleted from the server.</string>
|
||||
<string name="delete_warning_cloud_only_msg">%1$d out of %2$d document(s) have no local copy and will not be deleted from this device.</string>
|
||||
<string name="delete_confirm_button">Confirm</string>
|
||||
<string name="delete_cancel">Cancel</string>
|
||||
<string name="delete_error">Error while deleting</string>
|
||||
</resources>
|
||||
@@ -213,4 +213,18 @@
|
||||
<string name="scan_error_page">Impossible d\'enregistrer la page.</string>
|
||||
<string name="scan_error_export">Impossible d\'exporter les pages.</string>
|
||||
<string name="scan_export_done">Pages exportées</string>
|
||||
|
||||
<!-- Suppression multi-sélection -->
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="delete_locally">Supprimer localement</string>
|
||||
<string name="delete_in_cloud">Supprimer dans le cloud</string>
|
||||
<string name="delete_full">Supprimer partout</string>
|
||||
<string name="delete_confirm_title">Supprimer %1$d document(s) ?</string>
|
||||
<string name="delete_confirm_message">Cette action est irréversible.</string>
|
||||
<string name="delete_warning_title">Documents non synchronisés</string>
|
||||
<string name="delete_warning_local_only_msg">%1$d document(s) sur %2$d n\'est/sont pas synchronisés avec le cloud et ne seront pas supprimés du serveur.</string>
|
||||
<string name="delete_warning_cloud_only_msg">%1$d document(s) sur %2$d n\'ont pas de copie locale et ne seront pas supprimés de cet appareil.</string>
|
||||
<string name="delete_confirm_button">Confirmer</string>
|
||||
<string name="delete_cancel">Annuler</string>
|
||||
<string name="delete_error">Erreur lors de la suppression</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user