From 06361ae62f55c4e4e0a3b4dfe6cae72fbeabb953 Mon Sep 17 00:00:00 2001 From: m Date: Sat, 12 Sep 2026 19:21:36 +0200 Subject: [PATCH] create sub folders --- .../vaultdrop/mobile/features/saf/SafUris.kt | 16 +++++ .../mobile/ui/dashboard/DashboardScreen.kt | 65 +++++++++++++++++++ .../mobile/ui/folderlist/FolderListScreen.kt | 26 -------- .../mobile/ui/folderlist/FolderListUiState.kt | 5 +- .../ui/folderlist/FolderListViewModel.kt | 4 +- .../mobile/ui/navigation/FloatingNavBar.kt | 4 ++ .../mobile/ui/navigation/NavGraph.kt | 17 ++++- .../app/src/main/res/values-en/strings.xml | 3 +- .../app/src/main/res/values/strings.xml | 3 +- 9 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/dashboard/DashboardScreen.kt diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/features/saf/SafUris.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/features/saf/SafUris.kt index fdd2723..0f4a630 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/features/saf/SafUris.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/features/saf/SafUris.kt @@ -1,5 +1,6 @@ package com.vaultdrop.mobile.features.saf +import android.content.ContentResolver import android.net.Uri import android.provider.DocumentsContract @@ -11,8 +12,23 @@ import android.provider.DocumentsContract */ object SafUris { + /** + * Convertit une uri SAF en uri *document* utilisable par createDocument / + * moveDocument. + * + * Attention : `DocumentsContract.isTreeUri()` renvoie `true` pour toute uri + * commençant par `tree/`, y compris les uri *document* sous un tree + * (`tree//document/`). On doit donc les renvoyer telles + * quelles — sinon le `documentId` (ie. le sous-dossier) est remplacé par la + * racine de l'arbre, et un dossier/fichier ciblé sous un dossier imbriqué + * est créé/déplacé à la racine. + */ fun toDocumentUri(uri: String?): Uri? { val raw = uri?.let(Uri::parse) ?: return null + val segments = raw.pathSegments + val isDocumentUnderTree = raw.scheme == ContentResolver.SCHEME_CONTENT && + segments.size >= 3 && segments[0] == "tree" && segments[2] == "document" + if (isDocumentUnderTree) return raw return if (DocumentsContract.isTreeUri(raw)) { DocumentsContract.buildDocumentUriUsingTree( raw, diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/dashboard/DashboardScreen.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/dashboard/DashboardScreen.kt new file mode 100644 index 0000000..e4018c5 --- /dev/null +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/dashboard/DashboardScreen.kt @@ -0,0 +1,65 @@ +package com.vaultdrop.mobile.ui.dashboard + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vaultdrop.mobile.R +import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel +import com.vaultdrop.mobile.ui.components.ServerStatusBadge +import com.vaultdrop.mobile.ui.navigation.FloatingNavBar +import com.vaultdrop.mobile.ui.navigation.NavTab + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DashboardScreen( + selectedTab: NavTab, + onTabSelected: (NavTab) -> Unit, + connectionStatusViewModel: ConnectionStatusViewModel, +) { + val connectionStatus by connectionStatusViewModel.status.collectAsStateWithLifecycle() + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.dashboard)) }, + actions = { + ServerStatusBadge( + status = connectionStatus, + onClick = connectionStatusViewModel::checkNow, + ) + }, + ) + }, + bottomBar = { + FloatingNavBar(selected = selectedTab, onSelect = onTabSelected) + }, + ) { padding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.dashboard_coming_soon), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 32.dp), + ) + } + } +} \ 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 85a84d0..d4f82f6 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 @@ -32,7 +32,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ArrowUpward import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.CreateNewFolder -import androidx.compose.material.icons.filled.Dashboard import androidx.compose.material.icons.filled.Description import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.Refresh @@ -352,14 +351,6 @@ private fun HomeViewSelector( onClick = { onSelect(HomeView.FOLDERS) }, modifier = Modifier.weight(1f), ) - ViewSegment( - label = stringResource(R.string.view_dashboard), - icon = Icons.Filled.Dashboard, - selected = selected == HomeView.DASHBOARD, - enabled = enabled, - onClick = { onSelect(HomeView.DASHBOARD) }, - modifier = Modifier.weight(1f), - ) } } } @@ -431,7 +422,6 @@ private fun HomeViewContent( modifier: Modifier = Modifier, ) { when (view) { - HomeView.DASHBOARD -> DashboardPlaceholder(modifier = modifier) HomeView.FILES -> FileGridContent( sections = sections, isImporting = isImporting, @@ -456,22 +446,6 @@ private fun HomeViewContent( } } -/** Dashboard provisoire — vide pour l'instant. */ -@Composable -private fun DashboardPlaceholder(modifier: Modifier = Modifier) { - Box( - modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - Text( - text = stringResource(R.string.dashboard_coming_soon), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - } -} - /** Layer Fichiers : tous les fichiers visibles, groupés par jour. */ @Composable private fun FileGridContent( 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 08e6971..7e61a6c 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 @@ -7,7 +7,6 @@ import com.vaultdrop.mobile.data.local.entity.FolderEntity enum class HomeView { FILES, FOLDERS, - DASHBOARD, } /** Section d'un jour dans la grille d'accueil — miroir de `FileSection` (app/index.tsx). */ @@ -26,8 +25,8 @@ data class FilePair( ) data class FolderListUiState( - /** Segment actif de la page (Fichiers / Dossiers / Dashboard). */ - val view: HomeView = HomeView.DASHBOARD, + /** Segment actif de la page (Fichiers / Dossiers). */ + val view: HomeView = HomeView.FILES, /** Vrai pendant le déplacement d'une sélection : la vue Dossiers est forcée. */ val moveMode: Boolean = false, /** Vue à restaurer après déplacement/annulation. */ 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 5f888c7..b3cde3a 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 @@ -236,7 +236,7 @@ class FolderListViewModel @Inject constructor( /** Annule le mode déplacement : restaure la vue, la sélection est conservée. */ fun cancelMove() { - val previous = _uiState.value.viewBeforeMove ?: HomeView.DASHBOARD + val previous = _uiState.value.viewBeforeMove ?: HomeView.FILES _uiState.update { it.copy( moveMode = false, @@ -258,7 +258,7 @@ class FolderListViewModel @Inject constructor( } else { _uiState.update { it.copy(moveSuccess = true) } } - val previous = _uiState.value.viewBeforeMove ?: HomeView.DASHBOARD + val previous = _uiState.value.viewBeforeMove ?: HomeView.FILES _uiState.update { it.copy(moveMode = false, view = previous, viewBeforeMove = null) } 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 3753383..ba5e890 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 @@ -13,11 +13,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Dashboard 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.Search import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.Dashboard import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings @@ -42,6 +44,7 @@ enum class NavTab(val route: String) { FILES(Routes.FILES), SEARCH(Routes.SEARCH), SETTINGS(Routes.SETTINGS), + DASHBOARD(Routes.DASHBOARD), } private data class TabItem( @@ -53,6 +56,7 @@ private data class TabItem( private val TAB_ITEMS = listOf( TabItem(NavTab.FILES, Icons.Filled.Folder, Icons.Outlined.Folder, R.string.nav_files), + TabItem(NavTab.DASHBOARD, Icons.Filled.Dashboard, Icons.Outlined.Dashboard, R.string.nav_dashboard), TabItem(NavTab.SEARCH, Icons.Filled.Search, Icons.Outlined.Search, R.string.nav_search), TabItem(NavTab.SETTINGS, Icons.Filled.Settings, Icons.Outlined.Settings, R.string.nav_settings), ) diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt index 9974d38..b91d5d9 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/navigation/NavGraph.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -18,6 +19,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vaultdrop.mobile.features.connection.ConnectionStatusViewModel import com.vaultdrop.mobile.features.sync.SyncViewModel import com.vaultdrop.mobile.ui.auth.AuthViewModel +import com.vaultdrop.mobile.ui.dashboard.DashboardScreen import com.vaultdrop.mobile.ui.document.DocumentViewerScreen import com.vaultdrop.mobile.ui.folderdetail.FolderDetailScreen import com.vaultdrop.mobile.ui.folderlist.FolderListScreen @@ -29,6 +31,7 @@ object Routes { const val FILES = "files" const val SEARCH = "search" const val SETTINGS = "settings" + const val DASHBOARD = "dashboard" const val FOLDER_DETAIL = "folder/{folderResourceId}" const val ARG_FOLDER = "folderResourceId" const val DOCUMENT = "document/{documentResourceId}" @@ -45,6 +48,7 @@ private fun String?.toNavTab(): NavTab = when (this) { Routes.FILES -> NavTab.FILES Routes.SEARCH -> NavTab.SEARCH Routes.SETTINGS -> NavTab.SETTINGS + Routes.DASHBOARD -> NavTab.DASHBOARD else -> NavTab.FILES } @@ -64,8 +68,12 @@ fun NavGraph( val onTabSelected: (NavTab) -> Unit = { tab -> if (tab.route != selectedTab.route) { navController.navigate(tab.route) { - popUpTo(Routes.FILES) { inclusive = true } + // Sauve l'entrée courante (Fichiers) au lieu de la détruire : + // le ViewModel (et sa position d'exploration) survit au changement + // d'onglet, sinon « Créer un dossier » retombe sur la racine. + popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true + restoreState = true } } } @@ -102,6 +110,13 @@ fun NavGraph( connectionStatusViewModel = connectionStatusViewModel, ) } + composable(Routes.DASHBOARD) { + DashboardScreen( + selectedTab = selectedTab, + onTabSelected = onTabSelected, + connectionStatusViewModel = connectionStatusViewModel, + ) + } composable( route = Routes.FOLDER_DETAIL, arguments = listOf( 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 5261b65..f998b17 100644 --- a/mobile-kotlin/app/src/main/res/values-en/strings.xml +++ b/mobile-kotlin/app/src/main/res/values-en/strings.xml @@ -5,7 +5,6 @@ Folder Files Folders - Dashboard Dashboard coming soon Up Move here (%1$d) @@ -58,6 +57,8 @@ Files Search Settings + Dashboard + Dashboard Settings Dark mode Dark theme active diff --git a/mobile-kotlin/app/src/main/res/values/strings.xml b/mobile-kotlin/app/src/main/res/values/strings.xml index 417beb4..8a4d1b2 100644 --- a/mobile-kotlin/app/src/main/res/values/strings.xml +++ b/mobile-kotlin/app/src/main/res/values/strings.xml @@ -10,7 +10,6 @@ Dossier Fichiers Dossiers - Dashboard Dashboard à venir Remonter Déplacer ici (%1$d) @@ -59,6 +58,8 @@ Fichiers Recherche Réglages + Dashboard + Dashboard Réglages Mode sombre Thème sombre actif