feat(navigation): implement Bottom Navigation Bar with 4 tabs and app shell
MainScreen.kt: new app shell with Scaffold + Material 3 NavigationBar providing 4 tabs (Uebersicht, Inventur, Warnungen, Einstellungen). TopLevelDestination.kt: enum defining tab routes, icons (Home, Inventory2, Warning, Settings), and labels for the navigation bar. Screen.kt: added Warnings and Settings sealed interface members. KrisenvorratNavGraph.kt: accepts Modifier, added Warnings/Settings composables, removed obsolete DashboardScreen navigation callback. DashboardScreen.kt: removed Scaffold wrapper and onNavigateToItems param, now uses Column layout (TopAppBar handled inline). ItemListScreen.kt: removed onDashboardClick param and Dashboard menu entry (no longer needed with tab navigation). WarningsScreen.kt, SettingsScreen.kt: placeholder screens for future impl. MainActivity.kt: delegates to MainScreen instead of NavGraph directly. Added material-icons-extended dependency for Inventory2 icon. Closes #33
This commit is contained in:
parent
c88d10be10
commit
a4c0dc63b4
11 changed files with 214 additions and 47 deletions
|
|
@ -53,6 +53,7 @@ dependencies {
|
|||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.material.icons.extended)
|
||||
|
||||
// Hilt
|
||||
implementation(libs.hilt.android)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import de.krisenvorrat.app.ui.navigation.KrisenvorratNavGraph
|
||||
import de.krisenvorrat.app.ui.MainScreen
|
||||
import de.krisenvorrat.app.ui.theme.KrisenvorratTheme
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
|
@ -20,8 +19,7 @@ class MainActivity : ComponentActivity() {
|
|||
enableEdgeToEdge()
|
||||
setContent {
|
||||
KrisenvorratTheme {
|
||||
val navController = rememberNavController()
|
||||
KrisenvorratNavGraph(navController = navController)
|
||||
MainScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
72
app/src/main/java/de/krisenvorrat/app/ui/MainScreen.kt
Normal file
72
app/src/main/java/de/krisenvorrat/app/ui/MainScreen.kt
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package de.krisenvorrat.app.ui
|
||||
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import de.krisenvorrat.app.ui.navigation.KrisenvorratNavGraph
|
||||
import de.krisenvorrat.app.ui.navigation.TopLevelDestination
|
||||
|
||||
@Composable
|
||||
internal fun MainScreen() {
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
val showBottomBar = TopLevelDestination.entries.any { topLevel ->
|
||||
currentDestination?.hasRoute(topLevel.route::class) == true
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
if (showBottomBar) {
|
||||
NavigationBar {
|
||||
TopLevelDestination.entries.forEach { destination ->
|
||||
val isSelected = currentDestination?.hasRoute(destination.route::class) == true
|
||||
|
||||
NavigationBarItem(
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
navController.navigate(destination.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = if (isSelected) {
|
||||
destination.selectedIcon
|
||||
} else {
|
||||
destination.unselectedIcon
|
||||
},
|
||||
contentDescription = destination.label
|
||||
)
|
||||
},
|
||||
label = { Text(destination.label) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
KrisenvorratNavGraph(
|
||||
navController = navController,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.consumeWindowInsets(innerPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,16 +10,11 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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
|
||||
|
|
@ -42,38 +37,23 @@ import kotlin.math.roundToInt
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun DashboardScreen(
|
||||
onNavigateToItems: () -> Unit,
|
||||
viewModel: DashboardViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Dashboard") },
|
||||
actions = {
|
||||
IconButton(onClick = onNavigateToItems) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.List,
|
||||
contentDescription = "Artikelliste"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
TopAppBar(title = { Text("Übersicht") })
|
||||
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(16.dp)
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ internal fun ItemListScreen(
|
|||
onItemClick: (String) -> Unit,
|
||||
onCategoriesClick: () -> Unit,
|
||||
onLocationsClick: () -> Unit,
|
||||
onDashboardClick: () -> Unit,
|
||||
viewModel: ItemListViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -70,13 +69,6 @@ internal fun ItemListScreen(
|
|||
expanded = isMenuExpanded,
|
||||
onDismissRequest = { isMenuExpanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Dashboard") },
|
||||
onClick = {
|
||||
isMenuExpanded = false
|
||||
onDashboardClick()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Kategorien") },
|
||||
onClick = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package de.krisenvorrat.app.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
|
|
@ -9,19 +10,21 @@ import de.krisenvorrat.app.ui.dashboard.DashboardScreen
|
|||
import de.krisenvorrat.app.ui.item.ItemFormScreen
|
||||
import de.krisenvorrat.app.ui.item.ItemListScreen
|
||||
import de.krisenvorrat.app.ui.location.LocationListScreen
|
||||
import de.krisenvorrat.app.ui.settings.SettingsScreen
|
||||
import de.krisenvorrat.app.ui.warnings.WarningsScreen
|
||||
|
||||
@Composable
|
||||
internal fun KrisenvorratNavGraph(navController: NavHostController) {
|
||||
internal fun KrisenvorratNavGraph(
|
||||
navController: NavHostController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Dashboard
|
||||
startDestination = Screen.Dashboard,
|
||||
modifier = modifier
|
||||
) {
|
||||
composable<Screen.Dashboard> {
|
||||
DashboardScreen(
|
||||
onNavigateToItems = {
|
||||
navController.navigate(Screen.ItemList)
|
||||
}
|
||||
)
|
||||
DashboardScreen()
|
||||
}
|
||||
|
||||
composable<Screen.ItemList> {
|
||||
|
|
@ -37,11 +40,6 @@ internal fun KrisenvorratNavGraph(navController: NavHostController) {
|
|||
},
|
||||
onLocationsClick = {
|
||||
navController.navigate(Screen.LocationManagement)
|
||||
},
|
||||
onDashboardClick = {
|
||||
navController.navigate(Screen.Dashboard) {
|
||||
popUpTo(Screen.Dashboard) { inclusive = true }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -69,5 +67,13 @@ internal fun KrisenvorratNavGraph(navController: NavHostController) {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.Warnings> {
|
||||
WarningsScreen()
|
||||
}
|
||||
|
||||
composable<Screen.Settings> {
|
||||
SettingsScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,10 @@ internal sealed interface Screen {
|
|||
|
||||
@Serializable
|
||||
data object LocationManagement : Screen
|
||||
|
||||
@Serializable
|
||||
data object Warnings : Screen
|
||||
|
||||
@Serializable
|
||||
data object Settings : Screen
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package de.krisenvorrat.app.ui.navigation
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.outlined.Home
|
||||
import androidx.compose.material.icons.outlined.Inventory2
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
internal enum class TopLevelDestination(
|
||||
val route: Screen,
|
||||
val selectedIcon: ImageVector,
|
||||
val unselectedIcon: ImageVector,
|
||||
val label: String
|
||||
) {
|
||||
OVERVIEW(
|
||||
route = Screen.Dashboard,
|
||||
selectedIcon = Icons.Filled.Home,
|
||||
unselectedIcon = Icons.Outlined.Home,
|
||||
label = "Übersicht"
|
||||
),
|
||||
INVENTORY(
|
||||
route = Screen.ItemList,
|
||||
selectedIcon = Icons.Outlined.Inventory2,
|
||||
unselectedIcon = Icons.Outlined.Inventory2,
|
||||
label = "Inventur"
|
||||
),
|
||||
WARNINGS(
|
||||
route = Screen.Warnings,
|
||||
selectedIcon = Icons.Filled.Warning,
|
||||
unselectedIcon = Icons.Outlined.Warning,
|
||||
label = "Warnungen"
|
||||
),
|
||||
SETTINGS(
|
||||
route = Screen.Settings,
|
||||
selectedIcon = Icons.Filled.Settings,
|
||||
unselectedIcon = Icons.Outlined.Settings,
|
||||
label = "Einstellungen"
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package de.krisenvorrat.app.ui.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.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.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun SettingsScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
TopAppBar(title = { Text("Einstellungen") })
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Einstellungen werden in einem späteren Schritt implementiert.",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package de.krisenvorrat.app.ui.warnings
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.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.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun WarningsScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
TopAppBar(title = { Text("Warnungen") })
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Warnungen werden in einem späteren Schritt implementiert.",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
|
|||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
||||
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
|
||||
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
||||
|
|
|
|||
Loading…
Reference in a new issue