0.1.0
This commit is contained in:
274
app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt
Normal file
274
app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt
Normal file
@@ -0,0 +1,274 @@
|
||||
package com.atridad.openclimb.ui
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import com.atridad.openclimb.data.database.OpenClimbDatabase
|
||||
import com.atridad.openclimb.data.repository.ClimbRepository
|
||||
import com.atridad.openclimb.navigation.Screen
|
||||
import com.atridad.openclimb.navigation.bottomNavigationItems
|
||||
import com.atridad.openclimb.ui.screens.*
|
||||
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
|
||||
import com.atridad.openclimb.ui.viewmodel.ClimbViewModelFactory
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OpenClimbApp() {
|
||||
val navController = rememberNavController()
|
||||
val context = LocalContext.current
|
||||
val currentBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = currentBackStackEntry?.destination?.route
|
||||
|
||||
val database = remember { OpenClimbDatabase.getDatabase(context) }
|
||||
val repository = remember { ClimbRepository(database, context) }
|
||||
val viewModel: ClimbViewModel = viewModel(
|
||||
factory = ClimbViewModelFactory(repository)
|
||||
)
|
||||
|
||||
// FAB configuration
|
||||
var fabConfig by remember { mutableStateOf<FabConfig?>(null) }
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
OpenClimbBottomNavigation(navController = navController)
|
||||
},
|
||||
floatingActionButton = {
|
||||
fabConfig?.let { config ->
|
||||
FloatingActionButton(
|
||||
onClick = config.onClick,
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Icon(
|
||||
imageVector = config.icon,
|
||||
contentDescription = config.contentDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Sessions,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
// Main screens
|
||||
composable<Screen.Sessions> {
|
||||
val gyms by viewModel.gyms.collectAsState()
|
||||
val activeSession by viewModel.activeSession.collectAsState()
|
||||
LaunchedEffect(gyms, activeSession) {
|
||||
fabConfig = if (gyms.isNotEmpty() && activeSession == null) {
|
||||
FabConfig(
|
||||
icon = Icons.Default.Add,
|
||||
contentDescription = "Start Session",
|
||||
onClick = {
|
||||
if (gyms.size == 1) {
|
||||
viewModel.startSession(context, gyms.first().id)
|
||||
} else {
|
||||
navController.navigate(Screen.AddEditSession())
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
SessionsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateToSessionDetail = { sessionId ->
|
||||
navController.navigate(Screen.SessionDetail(sessionId))
|
||||
},
|
||||
onNavigateToAddSession = { gymId ->
|
||||
navController.navigate(Screen.AddEditSession(gymId = gymId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.Problems> {
|
||||
val gyms by viewModel.gyms.collectAsState()
|
||||
LaunchedEffect(gyms) {
|
||||
fabConfig = if (gyms.isNotEmpty()) {
|
||||
FabConfig(
|
||||
icon = Icons.Default.Add,
|
||||
contentDescription = "Add Problem",
|
||||
onClick = {
|
||||
navController.navigate(Screen.AddEditProblem())
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
ProblemsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateToProblemDetail = { problemId ->
|
||||
navController.navigate(Screen.ProblemDetail(problemId))
|
||||
},
|
||||
onNavigateToAddProblem = { gymId ->
|
||||
navController.navigate(Screen.AddEditProblem(gymId = gymId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.Analytics> {
|
||||
LaunchedEffect(Unit) {
|
||||
fabConfig = null // No FAB for analytics
|
||||
}
|
||||
AnalyticsScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
composable<Screen.Gyms> {
|
||||
LaunchedEffect(Unit) {
|
||||
fabConfig = FabConfig(
|
||||
icon = Icons.Default.Add,
|
||||
contentDescription = "Add Gym",
|
||||
onClick = {
|
||||
navController.navigate(Screen.AddEditGym())
|
||||
}
|
||||
)
|
||||
}
|
||||
GymsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateToGymDetail = { gymId ->
|
||||
navController.navigate(Screen.GymDetail(gymId))
|
||||
},
|
||||
onNavigateToAddGym = {
|
||||
navController.navigate(Screen.AddEditGym())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.Settings> {
|
||||
LaunchedEffect(Unit) {
|
||||
fabConfig = null // No FAB for settings
|
||||
}
|
||||
SettingsScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
// Detail screens
|
||||
composable<Screen.SessionDetail> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.SessionDetail>()
|
||||
SessionDetailScreen(
|
||||
sessionId = args.sessionId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToEdit = { sessionId ->
|
||||
navController.navigate(Screen.AddEditSession(sessionId = sessionId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.ProblemDetail> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.ProblemDetail>()
|
||||
ProblemDetailScreen(
|
||||
problemId = args.problemId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToEdit = { problemId ->
|
||||
navController.navigate(Screen.AddEditProblem(problemId = problemId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.GymDetail> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.GymDetail>()
|
||||
GymDetailScreen(
|
||||
gymId = args.gymId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToEdit = { gymId ->
|
||||
navController.navigate(Screen.AddEditGym(gymId = gymId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
composable<Screen.AddEditGym> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.AddEditGym>()
|
||||
AddEditGymScreen(
|
||||
gymId = args.gymId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.AddEditProblem> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.AddEditProblem>()
|
||||
AddEditProblemScreen(
|
||||
problemId = args.problemId,
|
||||
gymId = args.gymId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
|
||||
composable<Screen.AddEditSession> { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<Screen.AddEditSession>()
|
||||
AddEditSessionScreen(
|
||||
sessionId = args.sessionId,
|
||||
gymId = args.gymId,
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OpenClimbBottomNavigation(navController: NavHostController) {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
NavigationBar {
|
||||
bottomNavigationItems.forEach { item ->
|
||||
val isSelected = when (item.screen) {
|
||||
is Screen.Sessions -> currentRoute?.contains("Session") == true
|
||||
is Screen.Problems -> currentRoute?.contains("Problem") == true
|
||||
is Screen.Gyms -> currentRoute?.contains("Gym") == true
|
||||
is Screen.Analytics -> currentRoute?.contains("Analytics") == true
|
||||
is Screen.Settings -> currentRoute?.contains("Settings") == true
|
||||
else -> currentRoute?.contains(item.screen::class.simpleName ?: "") == true
|
||||
}
|
||||
|
||||
NavigationBarItem(
|
||||
icon = { Icon(item.icon, contentDescription = item.label) },
|
||||
label = { Text(item.label) },
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
navController.navigate(item.screen) {
|
||||
// Pop up to the start destination of the graph to
|
||||
// avoid building up a large stack of destinations
|
||||
// on the back stack as users select items
|
||||
popUpTo(Screen.Sessions) {
|
||||
saveState = true
|
||||
}
|
||||
// Avoid multiple copies of the same destination when
|
||||
// reselecting the same item
|
||||
launchSingleTop = true
|
||||
// Restore state when reselecting a previously selected item
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FabConfig(
|
||||
val icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
val contentDescription: String,
|
||||
val onClick: () -> Unit
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user