create sub folders
This commit is contained in:
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-26
@@ -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(
|
||||
|
||||
+2
-3
@@ -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. */
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user