navbar
This commit is contained in:
@@ -36,10 +36,14 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vaultdrop.mobile.R
|
||||
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)
|
||||
@Composable
|
||||
fun FolderListScreen(
|
||||
selectedTab: NavTab,
|
||||
onTabSelected: (NavTab) -> Unit,
|
||||
onOpenFolder: (String) -> Unit,
|
||||
viewModel: FolderListViewModel = hiltViewModel(),
|
||||
) {
|
||||
@@ -56,6 +60,9 @@ fun FolderListScreen(
|
||||
},
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
FloatingNavBar(selected = selectedTab, onSelect = onTabSelected)
|
||||
},
|
||||
) { padding ->
|
||||
FolderListContent(
|
||||
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
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.vaultdrop.mobile.ui.folderdetail.FolderDetailScreen
|
||||
import com.vaultdrop.mobile.ui.folderlist.FolderListScreen
|
||||
import com.vaultdrop.mobile.ui.search.SearchScreen
|
||||
import com.vaultdrop.mobile.ui.settings.SettingsScreen
|
||||
|
||||
object Routes {
|
||||
const val FILES = "files"
|
||||
const val SEARCH = "search"
|
||||
const val SETTINGS = "settings"
|
||||
const val FOLDER_DETAIL = "folder/{folderResourceId}"
|
||||
const val ARG_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
|
||||
fun NavGraph() {
|
||||
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(
|
||||
navController = navController,
|
||||
startDestination = Routes.FILES,
|
||||
) {
|
||||
composable(Routes.FILES) {
|
||||
FolderListScreen(
|
||||
selectedTab = selectedTab,
|
||||
onTabSelected = onTabSelected,
|
||||
onOpenFolder = { id -> navController.navigate(Routes.folder(id)) },
|
||||
)
|
||||
}
|
||||
composable(Routes.SEARCH) {
|
||||
SearchScreen(
|
||||
selectedTab = selectedTab,
|
||||
onTabSelected = onTabSelected,
|
||||
)
|
||||
}
|
||||
composable(Routes.SETTINGS) {
|
||||
SettingsScreen(
|
||||
selectedTab = selectedTab,
|
||||
onTabSelected = onTabSelected,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
route = Routes.FOLDER_DETAIL,
|
||||
arguments = listOf(
|
||||
|
||||
@@ -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="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 -->
|
||||
<string name="login_title">Sign in</string>
|
||||
<string name="login_subtitle">Sign in to your account</string>
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
<string name="refresh">Actualiser</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 -->
|
||||
<string name="login_title">Connexion</string>
|
||||
<string name="login_subtitle">Connecte-toi à ton compte</string>
|
||||
|
||||
Reference in New Issue
Block a user