diff --git a/app/src/main/java/com/cornellappdev/score/MainActivity.kt b/app/src/main/java/com/cornellappdev/score/MainActivity.kt index a375d6d..549924b 100644 --- a/app/src/main/java/com/cornellappdev/score/MainActivity.kt +++ b/app/src/main/java/com/cornellappdev/score/MainActivity.kt @@ -1,17 +1,14 @@ package com.cornellappdev.score -import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import com.cornellappdev.score.nav.root.RootNavigation import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : AppCompatActivity() { - @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() diff --git a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt new file mode 100644 index 0000000..3d98427 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt @@ -0,0 +1,46 @@ +package com.cornellappdev.score.nav + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.cornellappdev.score.nav.root.ScoreScreens +import com.cornellappdev.score.nav.root.ScoreScreens.Home +import com.cornellappdev.score.screen.GameDetailsScreen +import com.cornellappdev.score.screen.HomeScreen +import com.cornellappdev.score.screen.PastGamesScreen + +@Composable +fun ScoreNavHost(navController: NavHostController) { + // This ViewModelStoreOwner is used to scope the past and home screen view models to the root + // screen instead of their individual tabs. This way the view models are not reconstructed + // everytime you switch tabs. + val mainScreenViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) + + NavHost( + navController = navController, + startDestination = Home + ) { + composable { + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + HomeScreen(navigateToGameDetails = { + navController.navigate(ScoreScreens.GameDetailsPage("")) + }) + } + } + composable { + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + PastGamesScreen(navigateToGameDetails = { + navController.navigate(ScoreScreens.GameDetailsPage("")) + }) + } + } + composable { + GameDetailsScreen(onBackArrow = { + navController.navigateUp() + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavigationBar.kt b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavigationBar.kt new file mode 100644 index 0000000..30f72ac --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavigationBar.kt @@ -0,0 +1,61 @@ +package com.cornellappdev.score.nav + +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavBackStackEntry +import com.cornellappdev.score.nav.root.ScoreScreens +import com.cornellappdev.score.nav.root.tabs +import com.cornellappdev.score.nav.root.toScreen +import com.cornellappdev.score.theme.CrimsonPrimary +import com.cornellappdev.score.theme.GrayPrimary +import com.cornellappdev.score.theme.Style.bodyMedium +import com.cornellappdev.score.theme.White + +@Composable +fun ScoreNavigationBar( + navigateToScreen: (ScoreScreens) -> Unit, + navBackStackEntry: NavBackStackEntry?, + modifier: Modifier = Modifier, +) { + NavigationBar(modifier = modifier, containerColor = White) { + tabs.map { item -> + val isSelected = item.screen == navBackStackEntry?.toScreen() + + NavigationBarItem( + selected = isSelected, + onClick = { navigateToScreen(item.screen) }, + icon = { + Icon( + painter = painterResource(id = if (isSelected) item.selectedIcon else item.unselectedIcon), + contentDescription = null, + tint = Color.Unspecified + ) + }, + label = { + Text( + text = item.label, + style = bodyMedium, + color = if (isSelected) { + CrimsonPrimary + } else { + GrayPrimary + } + ) + } + ) + } + } +} + +@Preview +@Composable +private fun ScoreNavigationBarPreview() { + ScoreNavigationBar({}, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt index a3c8abc..3475ef4 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt @@ -7,34 +7,23 @@ import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize 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.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavBackStackEntry -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.cornellappdev.score.R -import com.cornellappdev.score.nav.root.ScoreRootScreens.Home.toScreen -import com.cornellappdev.score.screen.GameDetailsScreen -import com.cornellappdev.score.screen.HomeScreen -import com.cornellappdev.score.screen.PastGamesScreen -import com.cornellappdev.score.theme.CrimsonPrimary -import com.cornellappdev.score.theme.GrayPrimary +import com.cornellappdev.score.nav.ScoreNavHost +import com.cornellappdev.score.nav.ScoreNavigationBar +import com.cornellappdev.score.nav.root.ScoreScreens.GameDetailsPage +import com.cornellappdev.score.nav.root.ScoreScreens.Home +import com.cornellappdev.score.nav.root.ScoreScreens.ScoresScreen import com.cornellappdev.score.theme.LocalInfiniteLoading -import com.cornellappdev.score.theme.Style.bodyMedium -import com.cornellappdev.score.theme.White import kotlinx.serialization.Serializable @Composable @@ -67,65 +56,18 @@ fun RootNavigation( } } + Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = { - if (navBackStackEntry?.toScreen() is ScoreRootScreens.GameDetailsPage) { + if (navBackStackEntry?.toScreen() is GameDetailsPage) { return@Scaffold } - NavigationBar(containerColor = White) { - tabs.map { item -> - val isSelected = item.screen == navBackStackEntry?.toScreen() - - NavigationBarItem( - selected = isSelected, - onClick = { navController.navigate(item.screen) }, - icon = { - Icon( - painter = painterResource(id = if (isSelected) item.selectedIcon else item.unselectedIcon), - contentDescription = null, - tint = Color.Unspecified - ) - }, - label = { - Text( - text = item.label, - style = bodyMedium, - color = if (isSelected) { - CrimsonPrimary - } else { - GrayPrimary - } - ) - } - ) - } - } + ScoreNavigationBar({ navController.navigate(it) }, navBackStackEntry) } ) { innerPadding -> Box(modifier = Modifier.padding(innerPadding)) { CompositionLocalProvider(LocalInfiniteLoading provides animatedValue) { - NavHost( - navController = navController, - startDestination = ScoreRootScreens.Home - ) { - composable { - HomeScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage(it)) - }) - } - - composable { - GameDetailsScreen(onBackArrow = { - navController.navigateUp() - }) - } - - composable { - PastGamesScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage(it)) - }) - } - } + ScoreNavHost(navController) } } } @@ -133,27 +75,27 @@ fun RootNavigation( @Serializable -sealed class ScoreRootScreens { +sealed class ScoreScreens { @Serializable - data object Home : ScoreRootScreens() + data object Home : ScoreScreens() @Serializable - data class GameDetailsPage(val gameId: String) : ScoreRootScreens() + data class GameDetailsPage(val gameId: String) : ScoreScreens() @Serializable - data object ScoresScreen : ScoreRootScreens() - - fun NavBackStackEntry.toScreen(): ScoreRootScreens? = - when (destination.route?.substringAfterLast(".")?.substringBefore("/")) { - "Home" -> toRoute() - "GameDetailsPage" -> toRoute() - "ScoresScreen" -> toRoute() - else -> throw IllegalArgumentException("Invalid screen") - } + data object ScoresScreen : ScoreScreens() } +fun NavBackStackEntry.toScreen(): ScoreScreens? = + when (destination.route?.substringAfterLast(".")?.substringBefore("/")) { + "Home" -> toRoute() + "GameDetailsPage" -> toRoute() + "ScoresScreen" -> toRoute() + else -> throw IllegalArgumentException("Invalid screen") + } + data class NavItem( - val screen: ScoreRootScreens, + val screen: ScoreScreens, val label: String, val unselectedIcon: Int, val selectedIcon: Int @@ -164,12 +106,12 @@ val tabs = listOf( label = "Schedule", unselectedIcon = R.drawable.ic_schedule, selectedIcon = R.drawable.ic_schedule_filled, - screen = ScoreRootScreens.Home, + screen = ScoreScreens.Home, ), NavItem( label = "Scores", unselectedIcon = R.drawable.ic_scores, selectedIcon = R.drawable.ic_scores_filled, - screen = ScoreRootScreens.ScoresScreen, + screen = ScoreScreens.ScoresScreen, ), ) \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationRepository.kt b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationRepository.kt index ad70d3e..9f0073e 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationRepository.kt @@ -5,4 +5,4 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class RootNavigationRepository @Inject constructor() : BaseNavigationRepository() +class RootNavigationRepository @Inject constructor() : BaseNavigationRepository() diff --git a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationViewModel.kt b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationViewModel.kt index d185c17..8d40cb6 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigationViewModel.kt @@ -1,7 +1,7 @@ package com.cornellappdev.score.nav.root -import com.cornellappdev.score.viewmodel.BaseViewModel import com.cornellappdev.score.util.UIEvent +import com.cornellappdev.score.viewmodel.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -13,7 +13,7 @@ class RootNavigationViewModel @Inject constructor( ) { data class RootNavigationUiState( - val navigationEvent: UIEvent? = null, + val navigationEvent: UIEvent? = null, ) init {