navbar
This commit is contained in:
@@ -36,10 +36,14 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vaultdrop.mobile.R
|
import com.vaultdrop.mobile.R
|
||||||
import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
import com.vaultdrop.mobile.data.local.entity.FolderEntity
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.FloatingNavBar
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.NavTab
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FolderListScreen(
|
fun FolderListScreen(
|
||||||
|
selectedTab: NavTab,
|
||||||
|
onTabSelected: (NavTab) -> Unit,
|
||||||
onOpenFolder: (String) -> Unit,
|
onOpenFolder: (String) -> Unit,
|
||||||
viewModel: FolderListViewModel = hiltViewModel(),
|
viewModel: FolderListViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
@@ -56,6 +60,9 @@ fun FolderListScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
bottomBar = {
|
||||||
|
FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||||
|
},
|
||||||
) { padding ->
|
) { padding ->
|
||||||
FolderListContent(
|
FolderListContent(
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
|
package com.vaultdrop.mobile.ui.navigation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
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.Folder
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vaultdrop.mobile.R
|
||||||
|
|
||||||
|
enum class NavTab(val route: String) {
|
||||||
|
FILES(Routes.FILES),
|
||||||
|
SEARCH(Routes.SEARCH),
|
||||||
|
SETTINGS(Routes.SETTINGS),
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TabItem(
|
||||||
|
val tab: NavTab,
|
||||||
|
val filled: ImageVector,
|
||||||
|
val outlined: ImageVector,
|
||||||
|
val labelRes: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val TAB_ITEMS = listOf(
|
||||||
|
TabItem(NavTab.FILES, Icons.Filled.Folder, Icons.Outlined.Folder, R.string.nav_files),
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FloatingNavBar(
|
||||||
|
selected: NavTab,
|
||||||
|
onSelect: (NavTab) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 20.dp, end = 20.dp, bottom = 16.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(30.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
shadowElevation = 8.dp,
|
||||||
|
modifier = Modifier.widthIn(max = 400.dp).fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
TAB_ITEMS.forEach { item ->
|
||||||
|
val isActive = item.tab == selected
|
||||||
|
val icon = if (isActive) item.filled else item.outlined
|
||||||
|
val tint = if (isActive) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
onClick = { onSelect(item.tab) },
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
color = if (isActive) MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else MaterialTheme.colorScheme.surface,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 6.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = tint,
|
||||||
|
modifier = Modifier.size(22.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(item.labelRes),
|
||||||
|
color = tint,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
fontWeight = if (isActive) FontWeight.Bold else FontWeight.Medium,
|
||||||
|
modifier = Modifier.padding(top = 2.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,74 @@
|
|||||||
package com.vaultdrop.mobile.ui.navigation
|
package com.vaultdrop.mobile.ui.navigation
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
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
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
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
|
||||||
|
import com.vaultdrop.mobile.ui.search.SearchScreen
|
||||||
|
import com.vaultdrop.mobile.ui.settings.SettingsScreen
|
||||||
|
|
||||||
object Routes {
|
object Routes {
|
||||||
const val FILES = "files"
|
const val FILES = "files"
|
||||||
|
const val SEARCH = "search"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
const val FOLDER_DETAIL = "folder/{folderResourceId}"
|
const val FOLDER_DETAIL = "folder/{folderResourceId}"
|
||||||
const val ARG_FOLDER = "folderResourceId"
|
const val ARG_FOLDER = "folderResourceId"
|
||||||
|
|
||||||
fun folder(folderResourceId: String): String = "folder/$folderResourceId"
|
fun folder(folderResourceId: String): String = "folder/$folderResourceId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String?.toNavTab(): NavTab = when (this) {
|
||||||
|
Routes.FILES -> NavTab.FILES
|
||||||
|
Routes.SEARCH -> NavTab.SEARCH
|
||||||
|
Routes.SETTINGS -> NavTab.SETTINGS
|
||||||
|
else -> NavTab.FILES
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavGraph() {
|
fun NavGraph() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val selectedTab = backStackEntry?.destination?.route.toNavTab()
|
||||||
|
|
||||||
|
val onTabSelected: (NavTab) -> Unit = { tab ->
|
||||||
|
if (tab.route != selectedTab.route) {
|
||||||
|
navController.navigate(tab.route) {
|
||||||
|
popUpTo(Routes.FILES) { inclusive = true }
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Routes.FILES,
|
startDestination = Routes.FILES,
|
||||||
) {
|
) {
|
||||||
composable(Routes.FILES) {
|
composable(Routes.FILES) {
|
||||||
FolderListScreen(
|
FolderListScreen(
|
||||||
|
selectedTab = selectedTab,
|
||||||
|
onTabSelected = onTabSelected,
|
||||||
onOpenFolder = { id -> navController.navigate(Routes.folder(id)) },
|
onOpenFolder = { id -> navController.navigate(Routes.folder(id)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(Routes.SEARCH) {
|
||||||
|
SearchScreen(
|
||||||
|
selectedTab = selectedTab,
|
||||||
|
onTabSelected = onTabSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(Routes.SETTINGS) {
|
||||||
|
SettingsScreen(
|
||||||
|
selectedTab = selectedTab,
|
||||||
|
onTabSelected = onTabSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Routes.FOLDER_DETAIL,
|
route = Routes.FOLDER_DETAIL,
|
||||||
arguments = listOf(
|
arguments = listOf(
|
||||||
@@ -45,4 +85,4 @@ fun NavGraph() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.vaultdrop.mobile.ui.search
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import com.vaultdrop.mobile.R
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.FloatingNavBar
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.NavTab
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SearchScreen(
|
||||||
|
selectedTab: NavTab,
|
||||||
|
onTabSelected: (NavTab) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(title = { Text(stringResource(R.string.search)) })
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.search),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.vaultdrop.mobile.ui.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import com.vaultdrop.mobile.R
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.FloatingNavBar
|
||||||
|
import com.vaultdrop.mobile.ui.navigation.NavTab
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
selectedTab: NavTab,
|
||||||
|
onTabSelected: (NavTab) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(title = { Text(stringResource(R.string.settings)) })
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
<string name="refresh">Refresh</string>
|
<string name="refresh">Refresh</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<string name="nav_files">Files</string>
|
||||||
|
<string name="nav_search">Search</string>
|
||||||
|
<string name="nav_settings">Settings</string>
|
||||||
|
<string name="search">Search</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
|
||||||
<!-- Login -->
|
<!-- Login -->
|
||||||
<string name="login_title">Sign in</string>
|
<string name="login_title">Sign in</string>
|
||||||
<string name="login_subtitle">Sign in to your account</string>
|
<string name="login_subtitle">Sign in to your account</string>
|
||||||
|
|||||||
@@ -15,6 +15,13 @@
|
|||||||
<string name="refresh">Actualiser</string>
|
<string name="refresh">Actualiser</string>
|
||||||
<string name="back">Retour</string>
|
<string name="back">Retour</string>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<string name="nav_files">Fichiers</string>
|
||||||
|
<string name="nav_search">Recherche</string>
|
||||||
|
<string name="nav_settings">Réglages</string>
|
||||||
|
<string name="search">Recherche</string>
|
||||||
|
<string name="settings">Réglages</string>
|
||||||
|
|
||||||
<!-- Login -->
|
<!-- Login -->
|
||||||
<string name="login_title">Connexion</string>
|
<string name="login_title">Connexion</string>
|
||||||
<string name="login_subtitle">Connecte-toi à ton compte</string>
|
<string name="login_subtitle">Connecte-toi à ton compte</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user