diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8e702b9..bc8508bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = " androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } jetbrains-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.0" } +jetbrains-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version = "2.8.0-alpha10" } koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core" } diff --git a/tasks-app-android/src/main/assets/licenses_android.json b/tasks-app-android/src/main/assets/licenses_android.json index 0d64f33a..40e9967e 100644 --- a/tasks-app-android/src/main/assets/licenses_android.json +++ b/tasks-app-android/src/main/assets/licenses_android.json @@ -1054,6 +1054,91 @@ "Apache-2.0" ] }, + { + "uniqueId": "androidx.navigation:navigation-common", + "developers": [ + { + "name": "The Android Open Source Project" + } + ], + "artifactVersion": "2.8.0-rc01", + "description": "Android Navigation-Common", + "name": "Navigation Common", + "licenses": [ + "Apache-2.0" + ], + "organization": { + "name": "The Android Open Source Project" + } + }, + { + "uniqueId": "androidx.navigation:navigation-common-ktx", + "developers": [ + { + "name": "The Android Open Source Project" + } + ], + "artifactVersion": "2.8.0-rc01", + "description": "Android Navigation-Common-Ktx", + "name": "Navigation Common Kotlin Extensions", + "licenses": [ + "Apache-2.0" + ], + "organization": { + "name": "The Android Open Source Project" + } + }, + { + "uniqueId": "androidx.navigation:navigation-compose", + "developers": [ + { + "name": "The Android Open Source Project" + } + ], + "artifactVersion": "2.8.0-rc01", + "description": "Compose integration with Navigation", + "name": "Compose Navigation", + "licenses": [ + "Apache-2.0" + ], + "organization": { + "name": "The Android Open Source Project" + } + }, + { + "uniqueId": "androidx.navigation:navigation-runtime", + "developers": [ + { + "name": "The Android Open Source Project" + } + ], + "artifactVersion": "2.8.0-rc01", + "description": "Android Navigation-Runtime", + "name": "Navigation Runtime", + "licenses": [ + "Apache-2.0" + ], + "organization": { + "name": "The Android Open Source Project" + } + }, + { + "uniqueId": "androidx.navigation:navigation-runtime-ktx", + "developers": [ + { + "name": "The Android Open Source Project" + } + ], + "artifactVersion": "2.8.0-rc01", + "description": "Android Navigation-Runtime-Ktx", + "name": "Navigation Runtime Kotlin Extensions", + "licenses": [ + "Apache-2.0" + ], + "organization": { + "name": "The Android Open Source Project" + } + }, { "uniqueId": "androidx.profileinstaller:profileinstaller", "developers": [ @@ -2296,7 +2381,7 @@ "name": "Compose Multiplatform Team" } ], - "artifactVersion": "1.0.0", + "artifactVersion": "1.0.1", "description": "Provides Bundle in Kotlin Multiplatform projects", "name": "androidx.core:core-bundle", "licenses": [ @@ -2310,7 +2395,7 @@ "name": "Compose Multiplatform Team" } ], - "artifactVersion": "1.0.0", + "artifactVersion": "1.0.1", "description": "Provides Bundle in Kotlin Multiplatform projects", "name": "androidx.core:core-bundle", "licenses": [ diff --git a/tasks-app-desktop/src/main/resources/licenses_desktop.json b/tasks-app-desktop/src/main/resources/licenses_desktop.json index f2b4b6e6..bfc79a13 100644 --- a/tasks-app-desktop/src/main/resources/licenses_desktop.json +++ b/tasks-app-desktop/src/main/resources/licenses_desktop.json @@ -750,6 +750,48 @@ "Apache-2.0" ] }, + { + "uniqueId": "org.jetbrains.androidx.navigation:navigation-common-desktop", + "developers": [ + { + "name": "Compose Multiplatform Team" + } + ], + "artifactVersion": "2.8.0-alpha10", + "description": "Android Navigation-Common", + "name": "Navigation Common", + "licenses": [ + "Apache-2.0" + ] + }, + { + "uniqueId": "org.jetbrains.androidx.navigation:navigation-compose-desktop", + "developers": [ + { + "name": "Compose Multiplatform Team" + } + ], + "artifactVersion": "2.8.0-alpha10", + "description": "Compose integration with Navigation", + "name": "Compose Navigation", + "licenses": [ + "Apache-2.0" + ] + }, + { + "uniqueId": "org.jetbrains.androidx.navigation:navigation-runtime-desktop", + "developers": [ + { + "name": "Compose Multiplatform Team" + } + ], + "artifactVersion": "2.8.0-alpha10", + "description": "Android Navigation-Runtime", + "name": "Navigation Runtime", + "licenses": [ + "Apache-2.0" + ] + }, { "uniqueId": "org.jetbrains.androidx.savedstate:savedstate-desktop", "developers": [ @@ -757,7 +799,7 @@ "name": "Compose Multiplatform Team" } ], - "artifactVersion": "1.2.0", + "artifactVersion": "1.2.2", "description": "Android Lifecycle Saved State", "name": "Saved State", "licenses": [ diff --git a/tasks-app-shared/build.gradle.kts b/tasks-app-shared/build.gradle.kts index 4493c889..2a7e5e6d 100644 --- a/tasks-app-shared/build.gradle.kts +++ b/tasks-app-shared/build.gradle.kts @@ -83,6 +83,7 @@ kotlin { implementation(libs.bundles.coil) implementation(libs.jetbrains.lifecycle.viewmodel.compose) + implementation(libs.jetbrains.navigation.compose) implementation(project.dependencies.platform(libs.koin.bom)) implementation(libs.koin.core) diff --git a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/TasksApp.kt b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/TasksApp.kt index 075228b0..7ec63f0c 100644 --- a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/TasksApp.kt +++ b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/TasksApp.kt @@ -49,9 +49,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.dp +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import net.opatry.tasks.app.ui.component.EditTextDialog import net.opatry.tasks.app.ui.component.MissingScreen -import net.opatry.tasks.app.ui.component.MyBackHandler import net.opatry.tasks.app.ui.component.ProfileIcon import net.opatry.tasks.app.ui.screen.AboutApp import net.opatry.tasks.app.ui.screen.AboutScreen @@ -73,7 +79,6 @@ enum class AppTasksScreen( val contentDescription: StringResource? = null, ) { Tasks(Res.string.navigation_tasks, LucideIcons.ListTodo), - Calendar(Res.string.navigation_calendar, LucideIcons.Calendar), Search(Res.string.navigation_search, LucideIcons.Search), About(Res.string.navigation_about, LucideIcons.Info), @@ -81,7 +86,9 @@ enum class AppTasksScreen( @Composable fun TasksApp(aboutApp: AboutApp, userViewModel: UserViewModel, tasksViewModel: TaskListsViewModel) { - var selectedScreen by remember { mutableStateOf(AppTasksScreen.Tasks) } + val navController = rememberNavController() + val navBackStackEntry by navController.currentBackStackEntryAsState() + val userState by userViewModel.state.collectAsState(null) val isSigned by remember(userState) { derivedStateOf { @@ -97,18 +104,14 @@ fun TasksApp(aboutApp: AboutApp, userViewModel: UserViewModel, tasksViewModel: T var newTaskListDefaultTitle by remember { mutableStateOf("") } var showNewTaskListDialog by remember { mutableStateOf(false) } - MyBackHandler({ selectedScreen != AppTasksScreen.Tasks }) { - selectedScreen = AppTasksScreen.Tasks - } - NavigationSuiteScaffold(navigationSuiteItems = { AppTasksScreen.entries.forEach { screen -> // hide unsupported screens for now if (screen == AppTasksScreen.Calendar) return@forEach if (screen == AppTasksScreen.Search) return@forEach item( - selected = selectedScreen == screen, - onClick = { selectedScreen = screen }, + selected = navBackStackEntry?.destination?.hierarchy?.any { it.route == screen.name } == true, + onClick = { navController.navigateWithBackStackHandling(screen.name) }, label = { Text(stringResource(screen.labelRes)) }, icon = { Icon(screen.icon, screen.contentDescription?.let { stringResource(it) }) @@ -117,9 +120,9 @@ fun TasksApp(aboutApp: AboutApp, userViewModel: UserViewModel, tasksViewModel: T ) } }) { - Column { - when (selectedScreen) { - AppTasksScreen.Tasks -> { + NavHost(navController, AppTasksScreen.Tasks.name) { + composable(AppTasksScreen.Tasks.name) { + Column { Card( Modifier.padding(horizontal = 16.dp, vertical = 8.dp), shape = MaterialTheme.shapes.extraLarge, @@ -156,11 +159,30 @@ fun TasksApp(aboutApp: AboutApp, userViewModel: UserViewModel, tasksViewModel: T ) } } + } + + composable(AppTasksScreen.Calendar.name) { + MissingScreen(stringResource(AppTasksScreen.Calendar.labelRes), LucideIcons.Calendar) + } + + composable(AppTasksScreen.Search.name) { + MissingScreen(stringResource(AppTasksScreen.Search.labelRes), LucideIcons.Search) + } - AppTasksScreen.Calendar -> MissingScreen(stringResource(AppTasksScreen.Calendar.labelRes), LucideIcons.Calendar) - AppTasksScreen.Search -> MissingScreen(stringResource(AppTasksScreen.Search.labelRes), LucideIcons.Search) - AppTasksScreen.About -> AboutScreen(aboutApp) + composable(AppTasksScreen.About.name) { + AboutScreen(aboutApp) } } } } + +fun NavHostController.navigateWithBackStackHandling(route: String) { + // avoids stacking A/B/A/B/A/B when navigating back and forth between A and B using bottom sheet + navigate(route) { + popUpTo(graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } +} \ No newline at end of file