Skip to content

Commit

Permalink
[Reply] Add type safe navigation (#1459)
Browse files Browse the repository at this point in the history
Adding type safe navigation to Reply app now that the stable API has
been released.

I also had to add
```
kotlinOptions {
    jvmTarget = "17"
}
```

to avoid getting the following build error:

```
Inconsistent JVM-target compatibility detected for tasks 'compileDebugJavaWithJavac' (17) and 'compileDebugKotlin' (21).
```
  • Loading branch information
dturner authored Sep 26, 2024
2 parents d5b5ec0 + 4fa0a4f commit 5b9a06f
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 32 deletions.
6 changes: 6 additions & 0 deletions Reply/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.compose)
}

Expand Down Expand Up @@ -84,6 +85,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}

buildFeatures {
compose = true
}
Expand All @@ -100,6 +105,7 @@ dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)

implementation(libs.androidx.compose.ui.tooling.preview)
debugImplementation(libs.androidx.compose.ui.tooling)
Expand Down
17 changes: 8 additions & 9 deletions Reply/app/src/main/java/com/example/reply/ui/ReplyApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import com.example.reply.ui.navigation.ReplyNavigationActions
import com.example.reply.ui.navigation.ReplyNavigationWrapper
import com.example.reply.ui.navigation.ReplyRoute
import com.example.reply.ui.navigation.Route
import com.example.reply.ui.utils.DevicePosture
import com.example.reply.ui.utils.ReplyContentType
import com.example.reply.ui.utils.ReplyNavigationType
Expand Down Expand Up @@ -89,12 +89,11 @@ fun ReplyApp(
ReplyNavigationActions(navController)
}
val navBackStackEntry by navController.currentBackStackEntryAsState()
val selectedDestination =
navBackStackEntry?.destination?.route ?: ReplyRoute.INBOX
val currentDestination = navBackStackEntry?.destination

Surface {
ReplyNavigationWrapper(
selectedDestination = selectedDestination,
currentDestination = currentDestination,
navigateToTopLevelDestination = navigationActions::navigateTo
) {
ReplyNavHost(
Expand Down Expand Up @@ -126,9 +125,9 @@ private fun ReplyNavHost(
NavHost(
modifier = modifier,
navController = navController,
startDestination = ReplyRoute.INBOX,
startDestination = Route.Inbox,
) {
composable(ReplyRoute.INBOX) {
composable<Route.Inbox> {
ReplyInboxScreen(
contentType = contentType,
replyHomeUIState = replyHomeUIState,
Expand All @@ -139,13 +138,13 @@ private fun ReplyNavHost(
toggleSelectedEmail = toggleSelectedEmail
)
}
composable(ReplyRoute.DM) {
composable<Route.DirectMessages> {
EmptyComingSoon()
}
composable(ReplyRoute.ARTICLES) {
composable<Route.Articles> {
EmptyComingSoon()
}
composable(ReplyRoute.GROUPS) {
composable<Route.Groups> {
EmptyComingSoon()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import com.example.reply.R
import kotlinx.serialization.Serializable

object ReplyRoute {
const val INBOX = "Inbox"
const val ARTICLES = "Articles"
const val DM = "DirectMessages"
const val GROUPS = "Groups"
sealed interface Route {
@Serializable data object Inbox : Route
@Serializable data object Articles : Route
@Serializable data object DirectMessages : Route
@Serializable data object Groups : Route
}

data class ReplyTopLevelDestination(
val route: String,
val route: Route,
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector,
val iconTextId: Int
Expand All @@ -61,25 +62,25 @@ class ReplyNavigationActions(private val navController: NavHostController) {

val TOP_LEVEL_DESTINATIONS = listOf(
ReplyTopLevelDestination(
route = ReplyRoute.INBOX,
route = Route.Inbox,
selectedIcon = Icons.Default.Inbox,
unselectedIcon = Icons.Default.Inbox,
iconTextId = R.string.tab_inbox
),
ReplyTopLevelDestination(
route = ReplyRoute.ARTICLES,
route = Route.Articles,
selectedIcon = Icons.AutoMirrored.Filled.Article,
unselectedIcon = Icons.AutoMirrored.Filled.Article,
iconTextId = R.string.tab_article
),
ReplyTopLevelDestination(
route = ReplyRoute.DM,
route = Route.DirectMessages,
selectedIcon = Icons.Outlined.ChatBubbleOutline,
unselectedIcon = Icons.Outlined.ChatBubbleOutline,
iconTextId = R.string.tab_inbox
),
ReplyTopLevelDestination(
route = ReplyRoute.GROUPS,
route = Route.Groups,
selectedIcon = Icons.Default.People,
unselectedIcon = Icons.Default.People,
iconTextId = R.string.tab_article
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import androidx.compose.ui.unit.toSize
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
Expand All @@ -87,7 +89,7 @@ class ReplyNavSuiteScope(

@Composable
fun ReplyNavigationWrapper(
selectedDestination: String,
currentDestination: NavDestination?,
navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit,
content: @Composable ReplyNavSuiteScope.() -> Unit
) {
Expand Down Expand Up @@ -128,7 +130,7 @@ fun ReplyNavigationWrapper(
gesturesEnabled = gesturesEnabled,
drawerContent = {
ModalNavigationDrawerContent(
selectedDestination = selectedDestination,
currentDestination = currentDestination,
navigationContentPosition = navContentPosition,
navigateToTopLevelDestination = navigateToTopLevelDestination,
onDrawerClicked = {
Expand All @@ -144,11 +146,11 @@ fun ReplyNavigationWrapper(
navigationSuite = {
when (navLayoutType) {
NavigationSuiteType.NavigationBar -> ReplyBottomNavigationBar(
selectedDestination = selectedDestination,
currentDestination = currentDestination,
navigateToTopLevelDestination = navigateToTopLevelDestination
)
NavigationSuiteType.NavigationRail -> ReplyNavigationRail(
selectedDestination = selectedDestination,
currentDestination = currentDestination,
navigationContentPosition = navContentPosition,
navigateToTopLevelDestination = navigateToTopLevelDestination,
onDrawerClicked = {
Expand All @@ -158,7 +160,7 @@ fun ReplyNavigationWrapper(
}
)
NavigationSuiteType.NavigationDrawer -> PermanentNavigationDrawerContent(
selectedDestination = selectedDestination,
currentDestination = currentDestination,
navigationContentPosition = navContentPosition,
navigateToTopLevelDestination = navigateToTopLevelDestination
)
Expand All @@ -172,7 +174,7 @@ fun ReplyNavigationWrapper(

@Composable
fun ReplyNavigationRail(
selectedDestination: String,
currentDestination: NavDestination?,
navigationContentPosition: ReplyNavigationContentPosition,
navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit,
onDrawerClicked: () -> Unit = {},
Expand Down Expand Up @@ -219,7 +221,7 @@ fun ReplyNavigationRail(
) {
TOP_LEVEL_DESTINATIONS.forEach { replyDestination ->
NavigationRailItem(
selected = selectedDestination == replyDestination.route,
selected = currentDestination.hasRoute(replyDestination),
onClick = { navigateToTopLevelDestination(replyDestination) },
icon = {
Icon(
Expand All @@ -237,13 +239,13 @@ fun ReplyNavigationRail(

@Composable
fun ReplyBottomNavigationBar(
selectedDestination: String,
currentDestination: NavDestination?,
navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit
) {
NavigationBar(modifier = Modifier.fillMaxWidth()) {
TOP_LEVEL_DESTINATIONS.forEach { replyDestination ->
NavigationBarItem(
selected = selectedDestination == replyDestination.route,
selected = currentDestination.hasRoute(replyDestination),
onClick = { navigateToTopLevelDestination(replyDestination) },
icon = {
Icon(
Expand All @@ -258,7 +260,7 @@ fun ReplyBottomNavigationBar(

@Composable
fun PermanentNavigationDrawerContent(
selectedDestination: String,
currentDestination: NavDestination?,
navigationContentPosition: ReplyNavigationContentPosition,
navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit,
) {
Expand Down Expand Up @@ -313,7 +315,7 @@ fun PermanentNavigationDrawerContent(
) {
TOP_LEVEL_DESTINATIONS.forEach { replyDestination ->
NavigationDrawerItem(
selected = selectedDestination == replyDestination.route,
selected = currentDestination.hasRoute(replyDestination),
label = {
Text(
text = stringResource(id = replyDestination.iconTextId),
Expand Down Expand Up @@ -343,7 +345,7 @@ fun PermanentNavigationDrawerContent(

@Composable
fun ModalNavigationDrawerContent(
selectedDestination: String,
currentDestination: NavDestination?,
navigationContentPosition: ReplyNavigationContentPosition,
navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit,
onDrawerClicked: () -> Unit = {}
Expand Down Expand Up @@ -409,7 +411,7 @@ fun ModalNavigationDrawerContent(
) {
TOP_LEVEL_DESTINATIONS.forEach { replyDestination ->
NavigationDrawerItem(
selected = selectedDestination == replyDestination.route,
selected = currentDestination.hasRoute(replyDestination),
label = {
Text(
text = stringResource(id = replyDestination.iconTextId),
Expand Down Expand Up @@ -479,3 +481,6 @@ fun navigationMeasurePolicy(
enum class LayoutType {
HEADER, CONTENT
}

fun NavDestination?.hasRoute(destination: ReplyTopLevelDestination): Boolean =
this?.hasRoute(destination.route::class) ?: false
3 changes: 3 additions & 0 deletions Reply/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
kotlin = "2.0.20"
kotlinx_immutable = "0.3.8"
kotlinx-serialization-json = "1.6.3"
ksp = "2.0.20-1.0.24"
maps-compose = "3.1.1"
# @keep
Expand Down Expand Up @@ -153,6 +154,7 @@ kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.re
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_immutable" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" }
Expand All @@ -173,6 +175,7 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
Expand Down
3 changes: 3 additions & 0 deletions scripts/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
kotlin = "2.0.20"
kotlinx_immutable = "0.3.8"
kotlinx-serialization-json = "1.6.3"
ksp = "2.0.20-1.0.24"
maps-compose = "3.1.1"
# @keep
Expand Down Expand Up @@ -153,6 +154,7 @@ kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.re
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx_immutable" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" }
Expand All @@ -173,6 +175,7 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
Expand Down

0 comments on commit 5b9a06f

Please sign in to comment.