Migrate App Intro to Navigation 3 (#2983)

This commit is contained in:
Phil Oliver 2025-09-05 13:44:54 -04:00 committed by GitHub
parent 0cb0b19128
commit 4ab588cdaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 84 additions and 84 deletions

View file

@ -232,6 +232,7 @@ dependencies {
implementation(libs.bundles.adaptive)
implementation(libs.bundles.lifecycle)
implementation(libs.bundles.navigation)
implementation(libs.bundles.navigation3)
implementation(libs.bundles.coroutines)
implementation(libs.bundles.datastore)
implementation(libs.bundles.room)

View file

@ -23,14 +23,17 @@ import android.os.Build
import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entry
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.serialization.Serializable
/**
* Composable function for the main application introduction screen. This screen guides the user through initial setup
@ -43,7 +46,6 @@ import com.google.accompanist.permissions.rememberPermissionState
@Composable
fun AppIntroductionScreen(onDone: () -> Unit) {
val context = LocalContext.current
val navController = rememberNavController()
val notificationPermissionState: PermissionState? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@ -56,56 +58,76 @@ fun AppIntroductionScreen(onDone: () -> Unit) {
listOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
val locationPermissionState = rememberMultiplePermissionsState(permissions = locationPermissions)
NavHost(navController = navController, startDestination = IntroRoute.Welcome.route) {
composable(IntroRoute.Welcome.route) {
WelcomeScreen(onGetStarted = { navController.navigate(IntroRoute.Notifications.route) })
}
composable(IntroRoute.Notifications.route) {
val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true
NotificationsScreen(
showNextButton = notificationsAlreadyGranted,
onSkip = { navController.navigate(IntroRoute.Location.route) },
onConfigure = {
if (notificationsAlreadyGranted) {
navController.navigate(IntroRoute.CriticalAlerts.route)
} else {
// For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS
// For lower versions, notificationPermissionState will be null, and this branch isn't taken.
notificationPermissionState.launchPermissionRequest()
}
},
)
}
composable(IntroRoute.CriticalAlerts.route) {
CriticalAlertsScreen(
onSkip = { navController.navigate(IntroRoute.Location.route) },
onConfigure = {
// Intent to open the specific notification channel settings for "my_alerts"
// This allows the user to enable critical alerts if they were initially denied
// or to adjust settings for notifications that can bypass Do Not Disturb.
val intent =
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts")
val backStack = rememberNavBackStack(Welcome)
NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
entryProvider =
entryProvider {
entry<Welcome> { WelcomeScreen(onGetStarted = { backStack.add(Notifications) }) }
entry<Notifications> {
val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true
NotificationsScreen(
showNextButton = notificationsAlreadyGranted,
onSkip = {
// Skip this screen and the Critical Alerts screen. Proceed to Location screen.
backStack.add(Location)
},
onConfigure = {
if (notificationsAlreadyGranted) {
backStack.add(CriticalAlerts)
} else {
// For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS
// For lower versions, notificationPermissionState will be null, and this branch isn't
// taken.
notificationPermissionState?.launchPermissionRequest()
}
context.startActivity(intent)
navController.navigate(IntroRoute.Location.route)
},
)
}
composable(IntroRoute.Location.route) {
val locationAlreadyGranted = locationPermissionState.allPermissionsGranted
LocationScreen(
showNextButton = locationAlreadyGranted,
onSkip = onDone, // Callback to signify completion of the intro flow
onConfigure = {
if (locationAlreadyGranted) {
onDone() // Permissions already granted, proceed to finish
} else {
locationPermissionState.launchMultiplePermissionRequest()
}
},
)
}
}
},
)
}
entry<CriticalAlerts> {
CriticalAlertsScreen(
onSkip = { backStack.add(Location) },
onConfigure = {
// Intent to open the specific notification channel settings for "my_alerts"
// This allows the user to enable critical alerts if they were initially denied
// or to adjust settings for notifications that can bypass Do Not Disturb.
val intent =
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts")
}
context.startActivity(intent)
backStack.add(Location)
},
)
}
entry<Location> {
val locationAlreadyGranted = locationPermissionState.allPermissionsGranted
LocationScreen(
showNextButton = locationAlreadyGranted,
onSkip = onDone, // Callback to signify completion of the intro flow
onConfigure = {
if (locationAlreadyGranted) {
onDone() // Permissions already granted, proceed to finish
} else {
locationPermissionState.launchMultiplePermissionRequest()
}
},
)
}
},
)
}
@Serializable private data object Welcome : NavKey
@Serializable private data object Notifications : NavKey
@Serializable private data object CriticalAlerts : NavKey
@Serializable private data object Location : NavKey

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.intro
/** Sealed class defining type-safe navigation routes for the app introduction flow. */
sealed class IntroRoute(val route: String) {
object Welcome : IntroRoute("welcome")
object Notifications : IntroRoute("notifications")
object Location : IntroRoute("location")
object CriticalAlerts : IntroRoute("critical_alerts")
}

View file

@ -38,6 +38,7 @@ material = "1.13.0"
material3 = "1.5.0-alpha03"
mgrs = "2.1.3"
navigation = "2.9.3"
navigation3 = "1.0.0-alpha08"
okhttp = "5.1.0"
org-eclipse-paho-client-mqttv3 = "1.2.5"
osmbonuspack = "6.9.0"
@ -129,6 +130,8 @@ material = { group = "com.google.android.material", name = "material", version.r
mgrs = { group = "mil.nga", name = "mgrs", version.ref = "mgrs" }
navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" }
navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" }
navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" }
okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp3-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
org-eclipse-paho-client-mqttv3 = { group = "org.eclipse.paho", name = "org.eclipse.paho.client.mqttv3", version.ref = "org-eclipse-paho-client-mqttv3" }
@ -166,6 +169,9 @@ lifecycle = ["lifecycle-runtime-ktx", "lifecycle-livedata-ktx", "lifecycle-viewm
# Navigation
navigation = ["navigation-compose"]
# Navigation 3
navigation3 = ["navigation3-runtime", "navigation3-ui"]
# Coroutines
coroutines = ["kotlinx-coroutines-android", "kotlinx-coroutines-guava"]