Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion JetNews/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose)
alias(libs.plugins.kotlin.serialization)
}

android {
Expand Down Expand Up @@ -117,7 +118,10 @@ dependencies {
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.window)

androidTestImplementation(libs.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import com.example.jetnews.ui.theme.JetnewsTheme
@Composable
fun AppDrawer(
drawerState: DrawerState,
currentRoute: String,
currentRoute: JetnewsRoute?,
navigateToHome: () -> Unit,
navigateToInterests: () -> Unit,
closeDrawer: () -> Unit,
Expand All @@ -58,7 +58,7 @@ fun AppDrawer(
NavigationDrawerItem(
label = { Text(stringResource(id = R.string.home_title)) },
icon = { Icon(painterResource(R.drawable.ic_home), null) },
selected = currentRoute == JetnewsDestinations.HOME_ROUTE,
selected = currentRoute is Home,
onClick = {
navigateToHome()
closeDrawer()
Expand All @@ -68,7 +68,7 @@ fun AppDrawer(
NavigationDrawerItem(
label = { Text(stringResource(id = R.string.interests_title)) },
icon = { Icon(painterResource(R.drawable.ic_list_alt), null) },
selected = currentRoute == JetnewsDestinations.INTERESTS_ROUTE,
selected = currentRoute is Interests,
onClick = {
navigateToInterests()
closeDrawer()
Expand Down Expand Up @@ -102,7 +102,7 @@ fun PreviewAppDrawer() {
JetnewsTheme {
AppDrawer(
drawerState = rememberDrawerState(initialValue = DrawerValue.Open),
currentRoute = JetnewsDestinations.HOME_ROUTE,
currentRoute = Home(),
navigateToHome = {},
navigateToInterests = {},
closeDrawer = { },
Expand Down
39 changes: 23 additions & 16 deletions JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,37 @@ import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.jetnews.data.AppContainer
import com.example.jetnews.ui.components.AppNavRail
import com.example.jetnews.ui.theme.JetnewsTheme
import kotlinx.coroutines.launch

@Composable
fun JetnewsApp(appContainer: AppContainer, widthSizeClass: WindowWidthSizeClass) {
fun JetnewsApp(
appContainer: AppContainer,
widthSizeClass: WindowWidthSizeClass,
startRoute: Home = Home(),
) {
JetnewsTheme {
val navController = rememberNavController()
val navigationActions = remember(navController) {
JetnewsNavigationActions(navController)
val backStack = remember { mutableStateListOf<JetnewsRoute>(startRoute) }
val currentRoute: JetnewsRoute? = backStack.lastOrNull()

val navigateToHome: () -> Unit = {
// Pop everything back to Home (the start destination)
while (backStack.size > 1) backStack.removeLast()
}
val navigateToInterests: () -> Unit = {
if (backStack.lastOrNull() != Interests) {
while (backStack.size > 1) backStack.removeLast()
backStack.add(Interests)
}
}

val coroutineScope = rememberCoroutineScope()

val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute =
navBackStackEntry?.destination?.route ?: JetnewsDestinations.HOME_ROUTE

val isExpandedScreen = widthSizeClass == WindowWidthSizeClass.Expanded
val sizeAwareDrawerState = rememberSizeAwareDrawerState(isExpandedScreen)

Expand All @@ -55,8 +62,8 @@ fun JetnewsApp(appContainer: AppContainer, widthSizeClass: WindowWidthSizeClass)
AppDrawer(
drawerState = sizeAwareDrawerState,
currentRoute = currentRoute,
navigateToHome = navigationActions.navigateToHome,
navigateToInterests = navigationActions.navigateToInterests,
navigateToHome = navigateToHome,
navigateToInterests = navigateToInterests,
closeDrawer = { coroutineScope.launch { sizeAwareDrawerState.close() } },
)
},
Expand All @@ -68,14 +75,14 @@ fun JetnewsApp(appContainer: AppContainer, widthSizeClass: WindowWidthSizeClass)
if (isExpandedScreen) {
AppNavRail(
currentRoute = currentRoute,
navigateToHome = navigationActions.navigateToHome,
navigateToInterests = navigationActions.navigateToInterests,
navigateToHome = navigateToHome,
navigateToInterests = navigateToInterests,
)
}
JetnewsNavGraph(
appContainer = appContainer,
isExpandedScreen = isExpandedScreen,
navController = navController,
backStack = backStack,
openDrawer = { coroutineScope.launch { sizeAwareDrawerState.open() } },
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,66 +17,61 @@
package com.example.jetnews.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navDeepLink
import com.example.jetnews.JetnewsApplication.Companion.JETNEWS_APP_URI
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.jetnews.data.AppContainer
import com.example.jetnews.ui.home.HomeRoute
import com.example.jetnews.ui.home.HomeViewModel
import com.example.jetnews.ui.interests.InterestsRoute
import com.example.jetnews.ui.interests.InterestsViewModel

const val POST_ID = "postId"

@Composable
fun JetnewsNavGraph(
appContainer: AppContainer,
isExpandedScreen: Boolean,
backStack: SnapshotStateList<JetnewsRoute>,
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
openDrawer: () -> Unit = {},
startDestination: String = JetnewsDestinations.HOME_ROUTE,
) {
NavHost(
navController = navController,
startDestination = startDestination,
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
modifier = modifier,
) {
composable(
route = JetnewsDestinations.HOME_ROUTE,
deepLinks = listOf(
navDeepLink {
uriPattern =
"$JETNEWS_APP_URI/${JetnewsDestinations.HOME_ROUTE}?$POST_ID={$POST_ID}"
},
),
) { navBackStackEntry ->
val homeViewModel: HomeViewModel = viewModel(
factory = HomeViewModel.provideFactory(
postsRepository = appContainer.postsRepository,
preSelectedPostId = navBackStackEntry.arguments?.getString(POST_ID),
),
)
HomeRoute(
homeViewModel = homeViewModel,
isExpandedScreen = isExpandedScreen,
openDrawer = openDrawer,
)
}
composable(JetnewsDestinations.INTERESTS_ROUTE) {
val interestsViewModel: InterestsViewModel = viewModel(
factory = InterestsViewModel.provideFactory(appContainer.interestsRepository),
)
InterestsRoute(
interestsViewModel = interestsViewModel,
isExpandedScreen = isExpandedScreen,
openDrawer = openDrawer,
)
}
}
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
),
entryProvider = { route ->
when (route) {
is Home -> NavEntry(route) {
val homeViewModel: HomeViewModel = viewModel(
factory = HomeViewModel.provideFactory(
postsRepository = appContainer.postsRepository,
preSelectedPostId = route.preSelectedPostId,
),
)
HomeRoute(
homeViewModel = homeViewModel,
isExpandedScreen = isExpandedScreen,
openDrawer = openDrawer,
)
}
is Interests -> NavEntry(route) {
val interestsViewModel: InterestsViewModel = viewModel(
factory = InterestsViewModel.provideFactory(appContainer.interestsRepository),
)
InterestsRoute(
interestsViewModel = interestsViewModel,
isExpandedScreen = isExpandedScreen,
openDrawer = openDrawer,
)
}
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,17 @@

package com.example.jetnews.ui

import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

/**
* Destinations used in the [JetnewsApp].
* Route definitions used in [JetnewsApp].
*/
object JetnewsDestinations {
const val HOME_ROUTE = "home"
const val INTERESTS_ROUTE = "interests"
}
@Serializable
sealed interface JetnewsRoute : NavKey

/**
* Models the navigation actions in the app.
*/
class JetnewsNavigationActions(navController: NavHostController) {
val navigateToHome: () -> Unit = {
navController.navigate(JetnewsDestinations.HOME_ROUTE) {
// 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(navController.graph.findStartDestination().id) {
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
}
}
val navigateToInterests: () -> Unit = {
navController.navigate(JetnewsDestinations.INTERESTS_ROUTE) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
}
@Serializable
data class Home(val preSelectedPostId: String? = null) : JetnewsRoute

@Serializable
data object Interests : JetnewsRoute
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)

val appContainer = (application as JetnewsApplication).container

val postId = intent?.data?.getQueryParameter("postId")
val startRoute = Home(preSelectedPostId = postId)

setContent {
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
JetnewsApp(appContainer, widthSizeClass)
JetnewsApp(appContainer, widthSizeClass, startRoute)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.jetnews.R
import com.example.jetnews.ui.JetnewsDestinations
import com.example.jetnews.ui.Home
import com.example.jetnews.ui.Interests
import com.example.jetnews.ui.JetnewsRoute
import com.example.jetnews.ui.theme.JetnewsTheme

@Composable
fun AppNavRail(currentRoute: String, navigateToHome: () -> Unit, navigateToInterests: () -> Unit, modifier: Modifier = Modifier) {
fun AppNavRail(currentRoute: JetnewsRoute?, navigateToHome: () -> Unit, navigateToInterests: () -> Unit, modifier: Modifier = Modifier) {
NavigationRail(
header = {
Icon(
Expand All @@ -49,14 +51,14 @@ fun AppNavRail(currentRoute: String, navigateToHome: () -> Unit, navigateToInter
) {
Spacer(Modifier.weight(1f))
NavigationRailItem(
selected = currentRoute == JetnewsDestinations.HOME_ROUTE,
selected = currentRoute is Home,
onClick = navigateToHome,
icon = { Icon(painterResource(id = R.drawable.ic_home), stringResource(R.string.home_title)) },
label = { Text(stringResource(R.string.home_title)) },
alwaysShowLabel = false,
)
NavigationRailItem(
selected = currentRoute == JetnewsDestinations.INTERESTS_ROUTE,
selected = currentRoute is Interests,
onClick = navigateToInterests,
icon = { Icon(painterResource(id = R.drawable.ic_list_alt), stringResource(R.string.interests_title)) },
label = { Text(stringResource(R.string.interests_title)) },
Expand All @@ -72,7 +74,7 @@ fun AppNavRail(currentRoute: String, navigateToHome: () -> Unit, navigateToInter
fun PreviewAppNavRail() {
JetnewsTheme {
AppNavRail(
currentRoute = JetnewsDestinations.HOME_ROUTE,
currentRoute = Home(),
navigateToHome = {},
navigateToInterests = {},
)
Expand Down
5 changes: 5 additions & 0 deletions JetNews/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ androidx-lifecycle = "2.8.2"
androidx-lifecycle-compose = "2.10.0"
androidx-lifecycle-runtime-compose = "2.10.0"
androidx-navigation = "2.9.6"
androidx-navigation3 = "1.0.0"
androidx-lifecycle-viewmodel-navigation3 = "2.10.0"
androidx-palette = "1.0.0"
androidx-test = "1.7.0"
androidx-test-espresso = "3.7.0"
Expand Down Expand Up @@ -106,6 +108,9 @@ androidx-material-icons-core = { module = "androidx.compose.material:material-ic
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "androidx-navigation3" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "androidx-navigation3" }
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-lifecycle-viewmodel-navigation3" }
androidx-palette = { module = "androidx.palette:palette", version.ref = "androidx-palette" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
Expand Down