Revert "refactor(android): migrate to navigation safe-args"

This reverts commit 127a69249e.
This commit is contained in:
Harsh Shandilya 2024-05-08 19:24:05 +05:30
parent b69fa43d29
commit 68a5de84f7
10 changed files with 163 additions and 135 deletions

View File

@ -20,8 +20,6 @@ plugins {
alias(libs.plugins.baselineprofile) alias(libs.plugins.baselineprofile)
alias(libs.plugins.licensee) alias(libs.plugins.licensee)
alias(libs.plugins.tracelog) alias(libs.plugins.tracelog)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.navigation.safeargs)
} }
android { android {

View File

@ -22,9 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.ui.matches
import dev.msfjarvis.claw.android.ui.navigation.Destination
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
const val AnimationDuration = 100 const val AnimationDuration = 100
@ -53,13 +51,11 @@ fun ClawNavigationBar(
modifier = Modifier, modifier = Modifier,
) { ) {
NavigationBar(modifier = modifier) { NavigationBar(modifier = modifier) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
items.forEach { navItem -> items.forEach { navItem ->
val isSelected = currentDestination.matches(navItem.destination) val isCurrentDestination = navController.currentDestination?.route == navItem.route
NavigationBarItem( NavigationBarItem(
icon = { icon = {
Crossfade(isSelected, label = "nav-label") { Crossfade(isCurrentDestination, label = "nav-label") {
Icon( Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon, imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase), contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
@ -67,15 +63,16 @@ fun ClawNavigationBar(
} }
}, },
label = { Text(text = navItem.label) }, label = { Text(text = navItem.label) },
selected = isSelected, selected = isCurrentDestination,
onClick = { onClick = {
if (isSelected) { if (isCurrentDestination) {
navItem.listStateResetCallback() navItem.listStateResetCallback()
} else { } else {
navController.navigate(navItem.destination) { navController.graph.startDestinationRoute?.let { startDestination ->
popUpTo(navController.graph.startDestinationId) { saveState = true } navController.popBackStack(startDestination, false)
launchSingleTop = true }
restoreState = true if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
} }
} }
}, },
@ -88,7 +85,7 @@ fun ClawNavigationBar(
class NavigationItem( class NavigationItem(
val label: String, val label: String,
val destination: Destination, val route: String,
val icon: ImageVector, val icon: ImageVector,
val selectedIcon: ImageVector, val selectedIcon: ImageVector,
val listStateResetCallback: () -> Unit, val listStateResetCallback: () -> Unit,

View File

@ -22,8 +22,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.ui.matches
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
@ -50,14 +49,12 @@ fun ClawNavigationRail(
modifier = Modifier, modifier = Modifier,
) { ) {
NavigationRail(modifier = modifier) { NavigationRail(modifier = modifier) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
items.forEach { navItem -> items.forEach { navItem ->
val isSelected = currentDestination.matches(navItem.destination) val isCurrentDestination = navController.currentDestination?.route == navItem.route
NavigationRailItem( NavigationRailItem(
icon = { icon = {
Crossfade(isSelected, label = "nav-label") { Crossfade(isCurrentDestination, label = "nav-label") {
Icon( Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon, imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase), contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
@ -65,15 +62,16 @@ fun ClawNavigationRail(
} }
}, },
label = { Text(text = navItem.label) }, label = { Text(text = navItem.label) },
selected = isSelected, selected = isCurrentDestination,
onClick = { onClick = {
if (isSelected) { if (isCurrentDestination) {
navItem.listStateResetCallback() navItem.listStateResetCallback()
} else { } else {
navController.navigate(navItem.destination) { navController.graph.startDestinationRoute?.let { startDestination ->
popUpTo(navController.graph.startDestinationId) { saveState = true } navController.popBackStack(startDestination, false)
launchSingleTop = true }
restoreState = true if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
} }
} }
}, },

View File

@ -12,17 +12,12 @@ import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import dev.msfjarvis.claw.android.ui.navigation.Destinations
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.model.LinkMetadata import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.UIPost import dev.msfjarvis.claw.model.UIPost
import kotlinx.collections.immutable.ImmutableList
fun Context.getActivity(): ComponentActivity? { fun Context.getActivity(): ComponentActivity? {
return when (this) { return when (this) {
@ -47,7 +42,10 @@ fun rememberPostActions(
override fun viewComments(postId: String) { override fun viewComments(postId: String) {
viewModel.markPostAsRead(postId) viewModel.markPostAsRead(postId)
navController.navigate(Comments(postId)) val currentRoute = navController.currentDestination?.route
val newRoute =
Destinations.Comments.route.replace(Destinations.Comments.PLACEHOLDER, postId)
if (currentRoute != Destinations.Comments.route) navController.navigate(newRoute)
} }
override fun viewCommentsPage(post: UIPost) { override fun viewCommentsPage(post: UIPost) {
@ -68,21 +66,3 @@ fun rememberPostActions(
} }
} }
} }
/**
* Walk through the [NavDestination]'s [hierarchy] to see if it has any destination that matches the
* route defined by [dest].
*/
fun NavDestination?.matches(dest: Destination): Boolean {
return this?.hierarchy?.any { it.hasRoute(dest::class) } == true
}
/** Check if this [NavDestination] [matches] any of the potential navigation [destinations]. */
fun NavDestination?.any(destinations: ImmutableList<Destination>): Boolean {
return destinations.any { this?.matches(it) == true }
}
/** Check if this [NavDestination] [matches] none of the potential navigation [destinations]. */
fun NavDestination?.none(destinations: ImmutableList<Destination>): Boolean {
return destinations.none { this?.matches(it) == true }
}

View File

@ -1,27 +0,0 @@
/*
* Copyright © 2024 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.navigation
import kotlinx.serialization.Serializable
sealed interface Destination
@Serializable data object Hottest : Destination
@Serializable data object Newest : Destination
@Serializable data object Saved : Destination
@Serializable data class Comments(val postId: String) : Destination
@Serializable data class User(val username: String) : Destination
@Serializable data object Search : Destination
@Serializable data object Settings : Destination
@Serializable data object AboutLibraries : Destination

View File

@ -0,0 +1,50 @@
/*
* Copyright © 2024 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.navigation
sealed class Destinations {
abstract val route: String
data object Hottest : Destinations() {
override val route = "hottest"
}
data object Newest : Destinations() {
override val route = "newest"
}
data object Saved : Destinations() {
override val route = "saved"
}
data object Comments : Destinations() {
const val PLACEHOLDER = "{postId}"
override val route = "comments/$PLACEHOLDER"
}
data object User : Destinations() {
const val PLACEHOLDER = "{username}"
override val route = "user/$PLACEHOLDER"
}
data object Search : Destinations() {
override val route = "search"
}
data object Settings : Destinations() {
override val route = "settings"
}
data object AboutLibraries : Destinations() {
override val route = "about_libraries"
}
companion object {
val startDestination
get() = Hottest
}
}

View File

@ -52,33 +52,26 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute import androidx.navigation.navArgument
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel import com.deliveryhero.whetstone.compose.injectedViewModel
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dev.msfjarvis.claw.android.MainActivity import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.SearchActivity import dev.msfjarvis.claw.android.SearchActivity
import dev.msfjarvis.claw.android.ui.any
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
import dev.msfjarvis.claw.android.ui.getActivity import dev.msfjarvis.claw.android.ui.getActivity
import dev.msfjarvis.claw.android.ui.lists.DatabasePosts import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
import dev.msfjarvis.claw.android.ui.lists.NetworkPosts import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
import dev.msfjarvis.claw.android.ui.navigation.AboutLibraries
import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Comments import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import dev.msfjarvis.claw.android.ui.navigation.Settings
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.ui.none
import dev.msfjarvis.claw.android.ui.rememberPostActions import dev.msfjarvis.claw.android.ui.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage import dev.msfjarvis.claw.common.comments.CommentsPage
@ -88,7 +81,6 @@ import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile import dev.msfjarvis.claw.common.user.UserProfile
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@ -105,11 +97,11 @@ fun LobstersPostsScreen(
val newestListState = rememberLazyListState() val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState() val savedListState = rememberLazyListState()
val navController = rememberNavController() val navController = rememberNavController()
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val postActions = rememberPostActions(urlLauncher, navController, viewModel) val postActions = rememberPostActions(urlLauncher, navController, viewModel)
val backStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = backStackEntry?.destination?.route
val context = LocalContext.current val context = LocalContext.current
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems() val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
@ -121,7 +113,9 @@ fun LobstersPostsScreen(
val postIdOverride = context.getActivity()?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY) val postIdOverride = context.getActivity()?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY)
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (postIdOverride != null) { if (postIdOverride != null) {
navController.navigate(Comments(postIdOverride)) navController.navigate(
Destinations.Comments.route.replace(Destinations.Comments.PLACEHOLDER, postIdOverride)
)
} }
} }
@ -129,7 +123,7 @@ fun LobstersPostsScreen(
persistentListOf( persistentListOf(
NavigationItem( NavigationItem(
label = "Hottest", label = "Hottest",
destination = Hottest, route = Destinations.Hottest.route,
icon = Icons.Outlined.Whatshot, icon = Icons.Outlined.Whatshot,
selectedIcon = Icons.Filled.Whatshot, selectedIcon = Icons.Filled.Whatshot,
) { ) {
@ -139,7 +133,7 @@ fun LobstersPostsScreen(
}, },
NavigationItem( NavigationItem(
label = "Newest", label = "Newest",
destination = Newest, route = Destinations.Newest.route,
icon = Icons.Outlined.NewReleases, icon = Icons.Outlined.NewReleases,
selectedIcon = Icons.Filled.NewReleases, selectedIcon = Icons.Filled.NewReleases,
) { ) {
@ -149,7 +143,7 @@ fun LobstersPostsScreen(
}, },
NavigationItem( NavigationItem(
label = "Saved", label = "Saved",
destination = Saved, route = Destinations.Saved.route,
icon = Icons.Outlined.FavoriteBorder, icon = Icons.Outlined.FavoriteBorder,
selectedIcon = Icons.Filled.Favorite, selectedIcon = Icons.Filled.Favorite,
) { ) {
@ -158,14 +152,14 @@ fun LobstersPostsScreen(
} }
}, },
) )
val navDestinations = navItems.map(NavigationItem::destination).toPersistentList()
Scaffold( Scaffold(
topBar = { topBar = {
ClawAppBar( ClawAppBar(
navigationIcon = { navigationIcon = {
if ( if (
navController.previousBackStackEntry != null && currentDestination.none(navDestinations) navController.previousBackStackEntry != null &&
navItems.none { it.route == currentDestination }
) { ) {
IconButton( IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() } onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() }
@ -178,7 +172,7 @@ fun LobstersPostsScreen(
} }
}, },
title = { title = {
if (currentDestination.any(navDestinations)) { if (navItems.any { it.route == currentDestination }) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Image( Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground), painter = painterResource(id = R.drawable.ic_launcher_foreground),
@ -191,13 +185,13 @@ fun LobstersPostsScreen(
} }
}, },
actions = { actions = {
if (currentDestination.any(navDestinations)) { if (navItems.any { it.route == currentDestination }) {
IconButton( IconButton(
onClick = { context.startActivity(Intent(context, SearchActivity::class.java)) } onClick = { context.startActivity(Intent(context, SearchActivity::class.java)) }
) { ) {
Icon(imageVector = Icons.Filled.Search, contentDescription = "Search posts") Icon(imageVector = Icons.Filled.Search, contentDescription = "Search posts")
} }
IconButton(onClick = { navController.navigate(Settings) }) { IconButton(onClick = { navController.navigate(Destinations.Settings.route) }) {
Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings") Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings")
} }
} }
@ -209,7 +203,7 @@ fun LobstersPostsScreen(
ClawNavigationBar( ClawNavigationBar(
navController = navController, navController = navController,
items = navItems, items = navItems,
isVisible = currentDestination.any(navDestinations), isVisible = navItems.any { it.route == currentDestination },
) )
} }
}, },
@ -221,18 +215,18 @@ fun LobstersPostsScreen(
ClawNavigationRail( ClawNavigationRail(
navController = navController, navController = navController,
items = navItems, items = navItems,
isVisible = currentDestination.any(navDestinations), isVisible = navItems.any { it.route == currentDestination },
) )
} }
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = Hottest, startDestination = Destinations.startDestination.route,
// Make animations 2x faster than default specs // Make animations 2x faster than default specs
enterTransition = { fadeIn(animationSpec = tween(350)) }, enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) }, exitTransition = { fadeOut(animationSpec = tween(350)) },
) { ) {
composable<Hottest> { composable(route = Destinations.Hottest.route) {
setWebUri("https://lobste.rs/") setWebUri("https://lobste.rs/")
NetworkPosts( NetworkPosts(
lazyPagingItems = hottestPosts, lazyPagingItems = hottestPosts,
@ -240,7 +234,7 @@ fun LobstersPostsScreen(
postActions = postActions, postActions = postActions,
) )
} }
composable<Newest> { composable(route = Destinations.Newest.route) {
setWebUri("https://lobste.rs/") setWebUri("https://lobste.rs/")
NetworkPosts( NetworkPosts(
lazyPagingItems = newestPosts, lazyPagingItems = newestPosts,
@ -248,42 +242,64 @@ fun LobstersPostsScreen(
postActions = postActions, postActions = postActions,
) )
} }
composable<Saved> { composable(route = Destinations.Saved.route) {
setWebUri(null) setWebUri(null)
DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions) DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions)
} }
composable<Comments> { backStackEntry -> composable(
val postId = backStackEntry.toRoute<Comments>().postId route = Destinations.Comments.route,
setWebUri("https://lobste.rs/s/${postId}") arguments = listOf(navArgument("postId") { type = NavType.StringType }),
) { backStackEntry ->
val postId =
requireNotNull(backStackEntry.arguments?.getString("postId")) {
"Navigating to ${Destinations.Comments.route} without necessary 'postId' argument"
}
setWebUri("https://lobste.rs/s/$postId")
CommentsPage( CommentsPage(
postId = postId, postId = postId,
postActions = postActions, postActions = postActions,
htmlConverter = htmlConverter, htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments, getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments, markSeenComments = viewModel::markSeenComments,
openUserProfile = { navController.navigate(User(it)) }, openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
) )
} }
composable<User> { backStackEntry -> composable(
val username = backStackEntry.toRoute<User>().username route = Destinations.User.route,
setWebUri("https://lobste.rs/u/${username}") arguments = listOf(navArgument("username") { type = NavType.StringType }),
) { backStackEntry ->
val username =
requireNotNull(backStackEntry.arguments?.getString("username")) {
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
}
setWebUri("https://lobste.rs/u/$username")
UserProfile( UserProfile(
username = username, username = username,
getProfile = viewModel::getUserProfile, getProfile = viewModel::getUserProfile,
openUserProfile = { navController.navigate(User(it)) }, openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
) )
} }
composable<Settings> { composable(route = Destinations.Settings.route) {
SettingsScreen( SettingsScreen(
context = context, context = context,
openLibrariesScreen = { navController.navigate(AboutLibraries) }, openLibrariesScreen = { navController.navigate(Destinations.AboutLibraries.route) },
importPosts = viewModel::importPosts, importPosts = viewModel::importPosts,
exportPostsAsJson = viewModel::exportPostsAsJson, exportPostsAsJson = viewModel::exportPostsAsJson,
exportPostsAsHtml = viewModel::exportPostsAsHtml, exportPostsAsHtml = viewModel::exportPostsAsHtml,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
) )
} }
composable<Settings> { LibrariesContainer(modifier = Modifier.fillMaxSize()) } composable(route = Destinations.AboutLibraries.route) {
LibrariesContainer(modifier = Modifier.fillMaxSize())
}
} }
} }
} }

View File

@ -11,15 +11,14 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute import androidx.navigation.navArgument
import com.deliveryhero.whetstone.compose.injectedViewModel import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.ui.lists.SearchList import dev.msfjarvis.claw.android.ui.lists.SearchList
import dev.msfjarvis.claw.android.ui.navigation.Comments import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.ui.rememberPostActions import dev.msfjarvis.claw.android.ui.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage import dev.msfjarvis.claw.common.comments.CommentsPage
@ -41,10 +40,10 @@ fun SearchScreen(
Scaffold(modifier = modifier) { paddingValues -> Scaffold(modifier = modifier) { paddingValues ->
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = Search, startDestination = Destinations.Search.route,
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
) { ) {
composable<Search> { composable(route = Destinations.Search.route) {
setWebUri("https://lobste.rs/search") setWebUri("https://lobste.rs/search")
SearchList( SearchList(
items = viewModel.searchResults, items = viewModel.searchResults,
@ -54,8 +53,14 @@ fun SearchScreen(
setSearchQuery = { query -> viewModel.searchQuery = query }, setSearchQuery = { query -> viewModel.searchQuery = query },
) )
} }
composable<Comments> { backStackEntry -> composable(
val postId = backStackEntry.toRoute<Comments>().postId route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
) { backStackEntry ->
val postId =
requireNotNull(backStackEntry.arguments?.getString("postId")) {
"Navigating to ${Destinations.Comments.route} without necessary 'postId' argument"
}
setWebUri("https://lobste.rs/s/$postId") setWebUri("https://lobste.rs/s/$postId")
CommentsPage( CommentsPage(
postId = postId, postId = postId,
@ -63,16 +68,30 @@ fun SearchScreen(
htmlConverter = htmlConverter, htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments, getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments, markSeenComments = viewModel::markSeenComments,
openUserProfile = { navController.navigate(User(it)) }, openUserProfile = { username: String ->
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, username)
)
},
) )
} }
composable<User> { backStackEntry -> composable(
val username = backStackEntry.toRoute<User>().username route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
) { backStackEntry ->
val username =
requireNotNull(backStackEntry.arguments?.getString("username")) {
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
}
setWebUri("https://lobste.rs/u/$username") setWebUri("https://lobste.rs/u/$username")
UserProfile( UserProfile(
username = username, username = username,
getProfile = viewModel::getUserProfile, getProfile = viewModel::getUserProfile,
openUserProfile = { navController.navigate(User(it)) }, openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
) )
} }
} }

View File

@ -13,7 +13,6 @@ konvert = "3.2.0"
kotlin = "1.9.23" kotlin = "1.9.23"
kotlinResult = "2.0.0" kotlinResult = "2.0.0"
lifecycle = "2.8.0-rc01" lifecycle = "2.8.0-rc01"
navigation = "2.8.0-alpha08"
retrofit = "2.11.0" retrofit = "2.11.0"
richtext = "1.0.0-alpha01" richtext = "1.0.0-alpha01"
sentry-sdk = "7.8.0" sentry-sdk = "7.8.0"
@ -48,7 +47,7 @@ androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", ve
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }
androidx-lint = "androidx.lint:lint-gradle:1.0.0-alpha01" androidx-lint = "androidx.lint:lint-gradle:1.0.0-alpha01"
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" } androidx-navigation-compose = "androidx.navigation:navigation-compose:2.8.0-alpha08"
androidx-paging-compose = "androidx.paging:paging-compose:3.3.0-rc01" androidx-paging-compose = "androidx.paging:paging-compose:3.3.0-rc01"
androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.0-alpha01" androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.0-alpha01"
androidx-test-core = "androidx.test:core:1.6.0-alpha06" androidx-test-core = "androidx.test:core:1.6.0-alpha06"
@ -123,7 +122,6 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
ksp = "com.google.devtools.ksp:1.9.23-1.0.20" ksp = "com.google.devtools.ksp:1.9.23-1.0.20"
licensee = "app.cash.licensee:1.11.0" licensee = "app.cash.licensee:1.11.0"
modulegraphassert = "com.jraska.module.graph.assertion:2.5.0" modulegraphassert = "com.jraska.module.graph.assertion:2.5.0"
navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "navigation" }
poko = "dev.drewhamilton.poko:0.15.2" poko = "dev.drewhamilton.poko:0.15.2"
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
tracelog = "dev.msfjarvis.tracelog:0.1.3" tracelog = "dev.msfjarvis.tracelog:0.1.3"

View File

@ -14,7 +14,6 @@ pluginManagement {
includeGroup("androidx.baselineprofile") includeGroup("androidx.baselineprofile")
includeGroup("androidx.benchmark") includeGroup("androidx.benchmark")
includeGroup("androidx.databinding") includeGroup("androidx.databinding")
includeGroupAndSubgroups("androidx.navigation")
includeGroup("com.google.testing.platform") includeGroup("com.google.testing.platform")
includeGroupAndSubgroups("com.android") includeGroupAndSubgroups("com.android")
} }