create sub folders
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.vaultdrop.mobile.features.saf
|
package com.vaultdrop.mobile.features.saf
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
|
|
||||||
@@ -11,8 +12,23 @@ import android.provider.DocumentsContract
|
|||||||
*/
|
*/
|
||||||
object SafUris {
|
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? {
|
fun toDocumentUri(uri: String?): Uri? {
|
||||||
val raw = uri?.let(Uri::parse) ?: return null
|
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)) {
|
return if (DocumentsContract.isTreeUri(raw)) {
|
||||||
DocumentsContract.buildDocumentUriUsingTree(
|
DocumentsContract.buildDocumentUriUsingTree(
|
||||||
raw,
|
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.ArrowUpward
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.CreateNewFolder
|
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.Description
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.filled.Folder
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
@@ -352,14 +351,6 @@ private fun HomeViewSelector(
|
|||||||
onClick = { onSelect(HomeView.FOLDERS) },
|
onClick = { onSelect(HomeView.FOLDERS) },
|
||||||
modifier = Modifier.weight(1f),
|
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,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (view) {
|
when (view) {
|
||||||
HomeView.DASHBOARD -> DashboardPlaceholder(modifier = modifier)
|
|
||||||
HomeView.FILES -> FileGridContent(
|
HomeView.FILES -> FileGridContent(
|
||||||
sections = sections,
|
sections = sections,
|
||||||
isImporting = isImporting,
|
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. */
|
/** Layer Fichiers : tous les fichiers visibles, groupés par jour. */
|
||||||
@Composable
|
@Composable
|
||||||
private fun FileGridContent(
|
private fun FileGridContent(
|
||||||
|
|||||||
+2
-3
@@ -7,7 +7,6 @@ import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
|||||||
enum class HomeView {
|
enum class HomeView {
|
||||||
FILES,
|
FILES,
|
||||||
FOLDERS,
|
FOLDERS,
|
||||||
DASHBOARD,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Section d'un jour dans la grille d'accueil — miroir de `FileSection` (app/index.tsx). */
|
/** 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(
|
data class FolderListUiState(
|
||||||
/** Segment actif de la page (Fichiers / Dossiers / Dashboard). */
|
/** Segment actif de la page (Fichiers / Dossiers). */
|
||||||
val view: HomeView = HomeView.DASHBOARD,
|
val view: HomeView = HomeView.FILES,
|
||||||
/** Vrai pendant le déplacement d'une sélection : la vue Dossiers est forcée. */
|
/** Vrai pendant le déplacement d'une sélection : la vue Dossiers est forcée. */
|
||||||
val moveMode: Boolean = false,
|
val moveMode: Boolean = false,
|
||||||
/** Vue à restaurer après déplacement/annulation. */
|
/** 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. */
|
/** Annule le mode déplacement : restaure la vue, la sélection est conservée. */
|
||||||
fun cancelMove() {
|
fun cancelMove() {
|
||||||
val previous = _uiState.value.viewBeforeMove ?: HomeView.DASHBOARD
|
val previous = _uiState.value.viewBeforeMove ?: HomeView.FILES
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
moveMode = false,
|
moveMode = false,
|
||||||
@@ -258,7 +258,7 @@ class FolderListViewModel @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
_uiState.update { it.copy(moveSuccess = true) }
|
_uiState.update { it.copy(moveSuccess = true) }
|
||||||
}
|
}
|
||||||
val previous = _uiState.value.viewBeforeMove ?: HomeView.DASHBOARD
|
val previous = _uiState.value.viewBeforeMove ?: HomeView.FILES
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(moveMode = false, view = previous, viewBeforeMove = null)
|
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.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
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.DriveFileMove
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.filled.Folder
|
||||||
import androidx.compose.material.icons.filled.MergeType
|
import androidx.compose.material.icons.filled.MergeType
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Settings
|
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.Folder
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
@@ -42,6 +44,7 @@ enum class NavTab(val route: String) {
|
|||||||
FILES(Routes.FILES),
|
FILES(Routes.FILES),
|
||||||
SEARCH(Routes.SEARCH),
|
SEARCH(Routes.SEARCH),
|
||||||
SETTINGS(Routes.SETTINGS),
|
SETTINGS(Routes.SETTINGS),
|
||||||
|
DASHBOARD(Routes.DASHBOARD),
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class TabItem(
|
private data class TabItem(
|
||||||
@@ -53,6 +56,7 @@ private data class TabItem(
|
|||||||
|
|
||||||
private val TAB_ITEMS = listOf(
|
private val TAB_ITEMS = listOf(
|
||||||
TabItem(NavTab.FILES, Icons.Filled.Folder, Icons.Outlined.Folder, R.string.nav_files),
|
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.SEARCH, Icons.Filled.Search, Icons.Outlined.Search, R.string.nav_search),
|
||||||
TabItem(NavTab.SETTINGS, Icons.Filled.Settings, Icons.Outlined.Settings, R.string.nav_settings),
|
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.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
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.connection.ConnectionStatusViewModel
|
||||||
import com.vaultdrop.mobile.features.sync.SyncViewModel
|
import com.vaultdrop.mobile.features.sync.SyncViewModel
|
||||||
import com.vaultdrop.mobile.ui.auth.AuthViewModel
|
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.document.DocumentViewerScreen
|
||||||
import com.vaultdrop.mobile.ui.folderdetail.FolderDetailScreen
|
import com.vaultdrop.mobile.ui.folderdetail.FolderDetailScreen
|
||||||
import com.vaultdrop.mobile.ui.folderlist.FolderListScreen
|
import com.vaultdrop.mobile.ui.folderlist.FolderListScreen
|
||||||
@@ -29,6 +31,7 @@ object Routes {
|
|||||||
const val FILES = "files"
|
const val FILES = "files"
|
||||||
const val SEARCH = "search"
|
const val SEARCH = "search"
|
||||||
const val SETTINGS = "settings"
|
const val SETTINGS = "settings"
|
||||||
|
const val DASHBOARD = "dashboard"
|
||||||
const val FOLDER_DETAIL = "folder/{folderResourceId}"
|
const val FOLDER_DETAIL = "folder/{folderResourceId}"
|
||||||
const val ARG_FOLDER = "folderResourceId"
|
const val ARG_FOLDER = "folderResourceId"
|
||||||
const val DOCUMENT = "document/{documentResourceId}"
|
const val DOCUMENT = "document/{documentResourceId}"
|
||||||
@@ -45,6 +48,7 @@ private fun String?.toNavTab(): NavTab = when (this) {
|
|||||||
Routes.FILES -> NavTab.FILES
|
Routes.FILES -> NavTab.FILES
|
||||||
Routes.SEARCH -> NavTab.SEARCH
|
Routes.SEARCH -> NavTab.SEARCH
|
||||||
Routes.SETTINGS -> NavTab.SETTINGS
|
Routes.SETTINGS -> NavTab.SETTINGS
|
||||||
|
Routes.DASHBOARD -> NavTab.DASHBOARD
|
||||||
else -> NavTab.FILES
|
else -> NavTab.FILES
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +68,12 @@ fun NavGraph(
|
|||||||
val onTabSelected: (NavTab) -> Unit = { tab ->
|
val onTabSelected: (NavTab) -> Unit = { tab ->
|
||||||
if (tab.route != selectedTab.route) {
|
if (tab.route != selectedTab.route) {
|
||||||
navController.navigate(tab.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
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +110,13 @@ fun NavGraph(
|
|||||||
connectionStatusViewModel = connectionStatusViewModel,
|
connectionStatusViewModel = connectionStatusViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(Routes.DASHBOARD) {
|
||||||
|
DashboardScreen(
|
||||||
|
selectedTab = selectedTab,
|
||||||
|
onTabSelected = onTabSelected,
|
||||||
|
connectionStatusViewModel = connectionStatusViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Routes.FOLDER_DETAIL,
|
route = Routes.FOLDER_DETAIL,
|
||||||
arguments = listOf(
|
arguments = listOf(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
<string name="folder">Folder</string>
|
<string name="folder">Folder</string>
|
||||||
<string name="view_files">Files</string>
|
<string name="view_files">Files</string>
|
||||||
<string name="view_folders">Folders</string>
|
<string name="view_folders">Folders</string>
|
||||||
<string name="view_dashboard">Dashboard</string>
|
|
||||||
<string name="dashboard_coming_soon">Dashboard coming soon</string>
|
<string name="dashboard_coming_soon">Dashboard coming soon</string>
|
||||||
<string name="browse_up">Up</string>
|
<string name="browse_up">Up</string>
|
||||||
<string name="move_here_count">Move here (%1$d)</string>
|
<string name="move_here_count">Move here (%1$d)</string>
|
||||||
@@ -58,6 +57,8 @@
|
|||||||
<string name="nav_files">Files</string>
|
<string name="nav_files">Files</string>
|
||||||
<string name="nav_search">Search</string>
|
<string name="nav_search">Search</string>
|
||||||
<string name="nav_settings">Settings</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">Settings</string>
|
||||||
<string name="settings_dark_mode">Dark mode</string>
|
<string name="settings_dark_mode">Dark mode</string>
|
||||||
<string name="settings_dark_mode_on">Dark theme active</string>
|
<string name="settings_dark_mode_on">Dark theme active</string>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<string name="folder">Dossier</string>
|
<string name="folder">Dossier</string>
|
||||||
<string name="view_files">Fichiers</string>
|
<string name="view_files">Fichiers</string>
|
||||||
<string name="view_folders">Dossiers</string>
|
<string name="view_folders">Dossiers</string>
|
||||||
<string name="view_dashboard">Dashboard</string>
|
|
||||||
<string name="dashboard_coming_soon">Dashboard à venir</string>
|
<string name="dashboard_coming_soon">Dashboard à venir</string>
|
||||||
<string name="browse_up">Remonter</string>
|
<string name="browse_up">Remonter</string>
|
||||||
<string name="move_here_count">Déplacer ici (%1$d)</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_files">Fichiers</string>
|
||||||
<string name="nav_search">Recherche</string>
|
<string name="nav_search">Recherche</string>
|
||||||
<string name="nav_settings">Réglages</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">Réglages</string>
|
||||||
<string name="settings_dark_mode">Mode sombre</string>
|
<string name="settings_dark_mode">Mode sombre</string>
|
||||||
<string name="settings_dark_mode_on">Thème sombre actif</string>
|
<string name="settings_dark_mode_on">Thème sombre actif</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user