create sub folders

This commit is contained in:
m
2026-09-12 19:21:36 +02:00
parent b7eb291c84
commit 06361ae62f
9 changed files with 109 additions and 34 deletions
@@ -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/<treeId>/document/<docId>`). 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,
@@ -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),
)
}
}
}
@@ -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(
@@ -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. */
@@ -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)
}
@@ -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),
)
@@ -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(
@@ -5,7 +5,6 @@
<string name="folder">Folder</string>
<string name="view_files">Files</string>
<string name="view_folders">Folders</string>
<string name="view_dashboard">Dashboard</string>
<string name="dashboard_coming_soon">Dashboard coming soon</string>
<string name="browse_up">Up</string>
<string name="move_here_count">Move here (%1$d)</string>
@@ -58,6 +57,8 @@
<string name="nav_files">Files</string>
<string name="nav_search">Search</string>
<string name="nav_settings">Settings</string>
<string name="nav_dashboard">Dashboard</string>
<string name="dashboard">Dashboard</string>
<string name="settings">Settings</string>
<string name="settings_dark_mode">Dark mode</string>
<string name="settings_dark_mode_on">Dark theme active</string>
@@ -10,7 +10,6 @@
<string name="folder">Dossier</string>
<string name="view_files">Fichiers</string>
<string name="view_folders">Dossiers</string>
<string name="view_dashboard">Dashboard</string>
<string name="dashboard_coming_soon">Dashboard à venir</string>
<string name="browse_up">Remonter</string>
<string name="move_here_count">Déplacer ici (%1$d)</string>
@@ -59,6 +58,8 @@
<string name="nav_files">Fichiers</string>
<string name="nav_search">Recherche</string>
<string name="nav_settings">Réglages</string>
<string name="nav_dashboard">Dashboard</string>
<string name="dashboard">Dashboard</string>
<string name="settings">Réglages</string>
<string name="settings_dark_mode">Mode sombre</string>
<string name="settings_dark_mode_on">Thème sombre actif</string>