diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt index 9f9ebe5..09f265b 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailScreen.kt @@ -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(null) } + var pendingDeleteReview by remember { mutableStateOf(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 diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailViewModel.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailViewModel.kt index f34813b..d213058 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailViewModel.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderdetail/FolderDetailViewModel.kt @@ -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? = 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, 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) } + } } \ No newline at end of file diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt index 3b8c93d..4d9640c 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListScreen.kt @@ -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(null) } + var pendingDeleteReview by remember { mutableStateOf(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(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. */ diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListUiState.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListUiState.kt index 7e61a6c..02a466d 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListUiState.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListUiState.kt @@ -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, ) \ No newline at end of file diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListViewModel.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListViewModel.kt index b3cde3a..c2c4187 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListViewModel.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/folderlist/FolderListViewModel.kt @@ -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, 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 { diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/FloatingNavBar.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/FloatingNavBar.kt index ba5e890..66cc567 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/FloatingNavBar.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/FloatingNavBar.kt @@ -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), + ) } } } diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt index e5fc1d6..8ff77a9 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchScreen.kt @@ -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(null) } + var pendingDeleteReview by remember { mutableStateOf(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 diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchViewModel.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchViewModel.kt index cc9fbae..b33e8a6 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchViewModel.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/search/SearchViewModel.kt @@ -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(null) val moveError: StateFlow = _moveError.asStateFlow() + private val _deleteError = MutableStateFlow(null) + val deleteError: StateFlow = _deleteError.asStateFlow() + + private val _deleteSuccess = MutableStateFlow(false) + val deleteSuccess: StateFlow = _deleteSuccess.asStateFlow() + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) val uiState: StateFlow = 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, 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 { diff --git a/mobile-kotlin/app/src/main/res/values-en/strings.xml b/mobile-kotlin/app/src/main/res/values-en/strings.xml index ae978c6..3faf89b 100644 --- a/mobile-kotlin/app/src/main/res/values-en/strings.xml +++ b/mobile-kotlin/app/src/main/res/values-en/strings.xml @@ -133,4 +133,18 @@ Save PDF created Generated PDFs + + + Delete + Delete locally + Delete from cloud + Delete everywhere + Delete %1$d document(s)? + This action cannot be undone. + Documents not synced + %1$d out of %2$d document(s) are not synced to the cloud and will not be deleted from the server. + %1$d out of %2$d document(s) have no local copy and will not be deleted from this device. + Confirm + Cancel + Error while deleting \ No newline at end of file diff --git a/mobile-kotlin/app/src/main/res/values/strings.xml b/mobile-kotlin/app/src/main/res/values/strings.xml index b7b8660..43d068e 100644 --- a/mobile-kotlin/app/src/main/res/values/strings.xml +++ b/mobile-kotlin/app/src/main/res/values/strings.xml @@ -213,4 +213,18 @@ Impossible d\'enregistrer la page. Impossible d\'exporter les pages. Pages exportées + + + Supprimer + Supprimer localement + Supprimer dans le cloud + Supprimer partout + Supprimer %1$d document(s) ? + Cette action est irréversible. + Documents non synchronisés + %1$d document(s) sur %2$d n\'est/sont pas synchronisés avec le cloud et ne seront pas supprimés du serveur. + %1$d document(s) sur %2$d n\'ont pas de copie locale et ne seront pas supprimés de cet appareil. + Confirmer + Annuler + Erreur lors de la suppression \ No newline at end of file