refactor: eliminate Destination indirection

This commit is contained in:
Harsh Shandilya 2025-05-25 18:31:48 +05:30
parent 9dce06ff80
commit f64d4ab9a1
7 changed files with 34 additions and 40 deletions

View file

@ -17,7 +17,6 @@ plugins {
id("dev.msfjarvis.claw.kotlin-kapt") id("dev.msfjarvis.claw.kotlin-kapt")
id("dev.msfjarvis.claw.sentry") id("dev.msfjarvis.claw.sentry")
id("dev.msfjarvis.claw.versioning-plugin") id("dev.msfjarvis.claw.versioning-plugin")
id("kotlin-parcelize")
alias(libs.plugins.aboutlibraries) alias(libs.plugins.aboutlibraries)
alias(libs.plugins.android.junit5) alias(libs.plugins.android.junit5)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)

View file

@ -24,8 +24,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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.navigation3.runtime.NavKey
import dev.msfjarvis.claw.android.R import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.android.ui.navigation.Search import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.Settings import dev.msfjarvis.claw.android.ui.navigation.Settings
@ -34,8 +34,8 @@ import dev.msfjarvis.claw.android.ui.navigation.Settings
fun ClawAppBar( fun ClawAppBar(
activity: Activity?, activity: Activity?,
isTopLevel: Boolean, isTopLevel: Boolean,
navigateTo: (Destination) -> Unit, navigateTo: (NavKey) -> Unit,
popBackStack: () -> Destination?, popBackStack: () -> NavKey?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
TopAppBar( TopAppBar(

View file

@ -26,12 +26,12 @@ import androidx.compose.ui.graphics.Color
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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeEffect
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.common.ui.FloatingNavigationBar import dev.msfjarvis.claw.common.ui.FloatingNavigationBar
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -40,8 +40,8 @@ const val AnimationDuration = 100
@Composable @Composable
fun ClawNavigationBar( fun ClawNavigationBar(
items: ImmutableList<NavigationItem>, items: ImmutableList<NavigationItem>,
currentDestination: Destination?, currentNavKey: NavKey?,
navigateTo: (Destination) -> Unit, navigateTo: (NavKey) -> Unit,
isVisible: Boolean, isVisible: Boolean,
hazeState: HazeState, hazeState: HazeState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -84,7 +84,7 @@ fun ClawNavigationBar(
if (HazeDefaults.blurEnabled()) Color.Transparent else MaterialTheme.colorScheme.surface, if (HazeDefaults.blurEnabled()) Color.Transparent else MaterialTheme.colorScheme.surface,
) { ) {
items.forEach { navItem -> items.forEach { navItem ->
val isSelected = currentDestination == navItem.destination val isSelected = currentNavKey == navItem.navKey
NavigationBarItem( NavigationBarItem(
icon = { icon = {
Crossfade(isSelected, label = "nav-label") { Crossfade(isSelected, label = "nav-label") {
@ -100,7 +100,7 @@ fun ClawNavigationBar(
if (isSelected) { if (isSelected) {
navItem.listStateResetCallback() navItem.listStateResetCallback()
} else { } else {
navigateTo(navItem.destination) navigateTo(navItem.navKey)
} }
}, },
modifier = Modifier.testTag(navItem.label.uppercase()), modifier = Modifier.testTag(navItem.label.uppercase()),
@ -115,7 +115,7 @@ class NavigationItem
private constructor( private constructor(
val icon: ImageVector, val icon: ImageVector,
val label: String, val label: String,
val destination: Destination, val navKey: NavKey,
val selectedIcon: ImageVector, val selectedIcon: ImageVector,
val listStateResetCallback: () -> Unit, val listStateResetCallback: () -> Unit,
) { ) {
@ -125,7 +125,7 @@ private constructor(
) : this( ) : this(
destination.icon, destination.icon,
destination.label, destination.label,
destination.destination, destination.navKey,
destination.selectedIcon, destination.selectedIcon,
listStateResetCallback, listStateResetCallback,
) {} ) {}

View file

@ -21,14 +21,14 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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 dev.msfjarvis.claw.android.ui.navigation.Destination import androidx.navigation3.runtime.NavKey
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
fun ClawNavigationRail( fun ClawNavigationRail(
items: ImmutableList<NavigationItem>, items: ImmutableList<NavigationItem>,
currentDestination: Destination?, currentNavKey: NavKey?,
navigateTo: (Destination) -> Unit, navigateTo: (NavKey) -> Unit,
isVisible: Boolean, isVisible: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -51,7 +51,7 @@ fun ClawNavigationRail(
NavigationRail(modifier = modifier) { NavigationRail(modifier = modifier) {
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
items.forEach { navItem -> items.forEach { navItem ->
val isSelected = currentDestination == navItem.destination val isSelected = currentNavKey == navItem.navKey
NavigationRailItem( NavigationRailItem(
icon = { icon = {
Crossfade(isSelected, label = "nav-label") { Crossfade(isSelected, label = "nav-label") {
@ -67,7 +67,7 @@ fun ClawNavigationRail(
if (isSelected) { if (isSelected) {
navItem.listStateResetCallback() navItem.listStateResetCallback()
} else { } else {
navigateTo(navItem.destination) navigateTo(navItem.navKey)
} }
}, },
modifier = Modifier.testTag(navItem.label.uppercase()), modifier = Modifier.testTag(navItem.label.uppercase()),

View file

@ -22,7 +22,7 @@ import io.github.aakira.napier.Napier
* to the front while lists add behind. To counter these expectations with the actual backing data * to the front while lists add behind. To counter these expectations with the actual backing data
* structure, many APIs in this class inverse of identically named functions on [List]. * structure, many APIs in this class inverse of identically named functions on [List].
*/ */
class ClawBackStack<T : NavKey>(private val initialDestination: T) { class ClawBackStack(private val initialDestination: NavKey) {
/** /**
* Marker interface for destinations that occupy the "top" level of the back stack. * Marker interface for destinations that occupy the "top" level of the back stack.
* *
@ -41,7 +41,7 @@ class ClawBackStack<T : NavKey>(private val initialDestination: T) {
* from getting stuck in a frustratingly long stack of top level destinations that are so easily * from getting stuck in a frustratingly long stack of top level destinations that are so easily
* accessible that they have no reason to be on the stack. * accessible that they have no reason to be on the stack.
*/ */
fun add(destination: T) { fun add(destination: NavKey) {
logCurrentState("add") logCurrentState("add")
if (destination is TopLevelDestination) { if (destination is TopLevelDestination) {
backStack.clear() backStack.clear()
@ -59,17 +59,17 @@ class ClawBackStack<T : NavKey>(private val initialDestination: T) {
return (top != null && top is TopLevelDestination) return (top != null && top is TopLevelDestination)
} }
fun firstOrNull(): T? { fun firstOrNull(): NavKey? {
logCurrentState("firstOrNull") logCurrentState("firstOrNull")
return backStack.lastOrNull() return backStack.lastOrNull()
} }
fun lastOrNull(): T? { fun lastOrNull(): NavKey? {
logCurrentState("lastOrNull") logCurrentState("lastOrNull")
return backStack.firstOrNull() return backStack.firstOrNull()
} }
fun removeLastOrNull(): T? { fun removeLastOrNull(): NavKey? {
logCurrentState("removeLastOrNull") logCurrentState("removeLastOrNull")
return backStack.removeLastOrNull() return backStack.removeLastOrNull()
} }

View file

@ -6,7 +6,6 @@
*/ */
package dev.msfjarvis.claw.android.ui.navigation package dev.msfjarvis.claw.android.ui.navigation
import android.os.Parcelable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.NewReleases import androidx.compose.material.icons.filled.NewReleases
@ -17,49 +16,46 @@ import androidx.compose.material.icons.outlined.Whatshot
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import dev.msfjarvis.claw.android.ui.navigation.ClawBackStack.TopLevelDestination import dev.msfjarvis.claw.android.ui.navigation.ClawBackStack.TopLevelDestination
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
sealed interface Destination : Parcelable, NavKey @Serializable data object Hottest : NavKey, TopLevelDestination
@Parcelize @Serializable data object Hottest : Destination, TopLevelDestination @Serializable data object Newest : NavKey, TopLevelDestination
@Parcelize @Serializable data object Newest : Destination, TopLevelDestination @Serializable data object Saved : NavKey, TopLevelDestination
@Parcelize @Serializable data object Saved : Destination, TopLevelDestination @Serializable data class Comments(val postId: String) : NavKey
@Parcelize @Serializable data class Comments(val postId: String) : Destination @Serializable data class User(val username: String) : NavKey
@Parcelize @Serializable data class User(val username: String) : Destination @Serializable data object Search : NavKey
@Parcelize @Serializable data object Search : Destination @Serializable data object Settings : NavKey
@Parcelize @Serializable data object Settings : Destination @Serializable data object AboutLibraries : NavKey
@Parcelize @Serializable data object AboutLibraries : Destination
enum class AppDestinations( enum class AppDestinations(
val icon: ImageVector, val icon: ImageVector,
val label: String, val label: String,
val destination: Destination, val navKey: NavKey,
val selectedIcon: ImageVector, val selectedIcon: ImageVector,
) { ) {
HOTTEST( HOTTEST(
icon = Icons.Outlined.Whatshot, icon = Icons.Outlined.Whatshot,
label = "Hottest", label = "Hottest",
destination = Hottest, navKey = Hottest,
selectedIcon = Icons.Filled.Whatshot, selectedIcon = Icons.Filled.Whatshot,
), ),
NEWEST( NEWEST(
icon = Icons.Outlined.NewReleases, icon = Icons.Outlined.NewReleases,
label = "Newest", label = "Newest",
destination = Newest, navKey = Newest,
selectedIcon = Icons.Filled.NewReleases, selectedIcon = Icons.Filled.NewReleases,
), ),
SAVED( SAVED(
icon = Icons.Outlined.FavoriteBorder, icon = Icons.Outlined.FavoriteBorder,
label = "Saved", label = "Saved",
destination = Saved, navKey = Saved,
selectedIcon = Icons.Filled.Favorite, selectedIcon = Icons.Filled.Favorite,
), ),
} }

View file

@ -51,7 +51,6 @@ import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.ClawBackStack import dev.msfjarvis.claw.android.ui.navigation.ClawBackStack
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.Comments
import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.android.ui.navigation.Hottest import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved import dev.msfjarvis.claw.android.ui.navigation.Saved
@ -75,7 +74,7 @@ fun Nav3Screen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(), viewModel: ClawViewModel = injectedViewModel(),
) { ) {
val clawBackStack = ClawBackStack<Destination>(Hottest) val clawBackStack = ClawBackStack(Hottest)
// region Pain // region Pain
val context = LocalContext.current val context = LocalContext.current
@ -136,7 +135,7 @@ fun Nav3Screen(
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) { AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
ClawNavigationBar( ClawNavigationBar(
items = navItems, items = navItems,
currentDestination = currentDestination, currentNavKey = currentDestination,
navigateTo = { clawBackStack.add(it) }, navigateTo = { clawBackStack.add(it) },
isVisible = clawBackStack.isOnTopLevelDestination(), isVisible = clawBackStack.isOnTopLevelDestination(),
hazeState = hazeState, hazeState = hazeState,