diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 509e92f..243c527 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -100,7 +100,6 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.animation) - implementation(libs.androidx.navigation) implementation(libs.androidx.material3.adaptive) implementation(libs.androidx.ui) implementation(libs.androidx.ui.android) @@ -109,6 +108,8 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.material.icon.core) implementation(libs.androidx.material.icon.extended) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) // About Libraries implementation(libs.aboutlibraries.core) implementation(libs.aboutlibraries.compose) diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt index 1174db1..26f386a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/TodoEntity.kt @@ -4,7 +4,9 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import cn.super12138.todo.constants.Constants +import kotlinx.serialization.Serializable +@Serializable @Entity(tableName = Constants.DB_TABLE_NAME) data class TodoEntity( @ColumnInfo(name = "content") val content: String, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index a22a290..1b44314 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -17,11 +17,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import cn.super12138.todo.R import cn.super12138.todo.utils.VibrationUtils -import com.kyant.capsule.ContinuousRoundedRectangle @Composable fun ConfirmDialog( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt index 7a8d72a..f5804c0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt @@ -4,10 +4,11 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import cn.super12138.todo.ui.pages.editor.TodoEditorPage import cn.super12138.todo.ui.pages.main.MainPage import cn.super12138.todo.ui.pages.settings.SettingsAbout @@ -19,8 +20,7 @@ import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptions import cn.super12138.todo.ui.pages.settings.SettingsDeveloperOptionsPadding import cn.super12138.todo.ui.pages.settings.SettingsInterface import cn.super12138.todo.ui.pages.settings.SettingsMain -import cn.super12138.todo.ui.theme.materialSharedAxisXIn -import cn.super12138.todo.ui.theme.materialSharedAxisXOut +import cn.super12138.todo.ui.theme.materialSharedAxisX import cn.super12138.todo.ui.viewmodels.MainViewModel private const val INITIAL_OFFSET_FACTOR = 0.10f @@ -29,126 +29,117 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f @Composable fun TodoNavigation( modifier: Modifier = Modifier, - navController: NavHostController = rememberNavController(), - startDestination: String = TodoScreen.Main.name, + backStack: NavBackStack = rememberNavBackStack(TodoScreen.Main), viewModel: MainViewModel ) { + fun onBack() { + backStack.removeAt(backStack.lastIndex) + } + SharedTransitionLayout { - NavHost( - navController = navController, - startDestination = startDestination, - enterTransition = { - materialSharedAxisXIn( - initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } - ) + NavDisplay( + backStack = backStack, + onBack = ::onBack, + transitionSpec = { + materialSharedAxisX( + initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) }, - exitTransition = { - materialSharedAxisXOut( - targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) + popTransitionSpec = { + materialSharedAxisX( + initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, + targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) }, - popEnterTransition = { - materialSharedAxisXIn( - initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) + + entryProvider = entryProvider { + entry { + MainPage( + viewModel = viewModel, + toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) }, + toSettingsPage = { backStack.add(TodoScreen.SettingsMain) }, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + entry { editorArgs -> + TodoEditorPage( + toDo = editorArgs.toDo, + onSave = { + viewModel.addTodo(it) + // 如果原来的待办状态为未完成并且修改后状态为完成 + if (editorArgs.toDo?.isCompleted != true && it.isCompleted) { + viewModel.playConfetti() + } + onBack() + }, + onDelete = { + if (editorArgs.toDo !== null) { + viewModel.deleteTodo(editorArgs.toDo) + } + onBack() + }, + onNavigateUp = ::onBack, + sharedTransitionScope = this@SharedTransitionLayout + ) + } + + entry { + SettingsMain( + toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) }, + toAboutPage = { backStack.add(TodoScreen.SettingsAbout) }, + toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) }, + toDataPage = { backStack.add(TodoScreen.SettingsData) }, + onNavigateUp = ::onBack, + ) + } + + entry { + SettingsAppearance(onNavigateUp = ::onBack) + } + + entry { + SettingsInterface(onNavigateUp = ::onBack) + } + + entry { + SettingsData( + viewModel = viewModel, + toCategoryManager = { backStack.add(TodoScreen.SettingsDataCategory) }, + onNavigateUp = ::onBack + ) + } + + entry { + SettingsDataCategory(onNavigateUp = ::onBack) + } + + entry { + SettingsAbout( + //toSpecialPage = { backStack.add(TodoScreen.SettingsAboutSpecial) }, + toLicencePage = { backStack.add(TodoScreen.SettingsAboutLicence) }, + toDevPage = { backStack.add(TodoScreen.SettingsDev) }, + onNavigateUp = ::onBack, + ) + } + + /*entry { + SettingsAboutSpecial(viewModel = viewModel) + }*/ + + entry { + SettingsAboutLicence(onNavigateUp = ::onBack) + } + + entry { + SettingsDeveloperOptions( + toPaddingPage = { backStack.add(TodoScreen.SettingsDevPadding) }, + onNavigateUp = ::onBack + ) + } + entry { + SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack) + } }, - popExitTransition = { - materialSharedAxisXOut( - targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } - ) - }, - modifier = modifier - ) { - composable(TodoScreen.Main.name) { - MainPage( - viewModel = viewModel, - toTodoEditPage = { navController.navigate(TodoScreen.TodoEditor.name) }, - toSettingsPage = { navController.navigate(TodoScreen.SettingsMain.name) }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = this@composable - ) - } - - composable(TodoScreen.TodoEditor.name) { - TodoEditorPage( - toDo = viewModel.selectedEditTodo, - onSave = { - viewModel.addTodo(it) - // 如果原来的待办状态为未完成并且修改后状态为完成 - if (viewModel.selectedEditTodo?.isCompleted != true && it.isCompleted) { - viewModel.playConfetti() - } - navController.navigateUp() - }, - onDelete = { - if (viewModel.selectedEditTodo !== null) { - viewModel.deleteTodo(viewModel.selectedEditTodo!!) - viewModel.setEditTodoItem(null) - } - navController.navigateUp() - }, - onNavigateUp = { navController.navigateUp() }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedVisibilityScope = this@composable - ) - } - - composable(TodoScreen.SettingsMain.name) { - SettingsMain( - toAppearancePage = { navController.navigate(TodoScreen.SettingsAppearance.name) }, - toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }, - toInterfacePage = { navController.navigate(TodoScreen.SettingsInterface.name) }, - toDataPage = { navController.navigate(TodoScreen.SettingsData.name) }, - onNavigateUp = { navController.navigateUp() }, - ) - } - - composable(TodoScreen.SettingsAppearance.name) { - SettingsAppearance(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsInterface.name) { - SettingsInterface(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsData.name) { - SettingsData( - viewModel = viewModel, - toCategoryManager = { navController.navigate(TodoScreen.SettingsDataCategory.name) }, - onNavigateUp = { navController.navigateUp() } - ) - } - - composable(TodoScreen.SettingsDataCategory.name) { - SettingsDataCategory(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsAbout.name) { - SettingsAbout( - //toSpecialPage = { navController.navigate(TodoScreen.SettingsAboutSpecial.name) }, - toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) }, - toDevPage = { navController.navigate(TodoScreen.SettingsDev.name) }, - onNavigateUp = { navController.navigateUp() }, - ) - } - - /*composable(TodoScreen.SettingsAboutSpecial.name) { - SettingsAboutSpecial(viewModel = viewModel) - }*/ - - composable(TodoScreen.SettingsAboutLicence.name) { - SettingsAboutLicence(onNavigateUp = { navController.navigateUp() }) - } - - composable(TodoScreen.SettingsDev.name) { - SettingsDeveloperOptions( - toPaddingPage = { navController.navigate(TodoScreen.SettingsDevPadding.name) }, - onNavigateUp = { navController.navigateUp() } - ) - } - composable(TodoScreen.SettingsDevPadding.name) { - SettingsDeveloperOptionsPadding(onNavigateUp = { navController.navigateUp() }) - } - } + modifier = modifier, + ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt index 0ebb9cf..7de325c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt @@ -1,17 +1,44 @@ package cn.super12138.todo.ui.navigation -enum class TodoScreen { - Main, - TodoEditor, - SettingsMain, - SettingsAppearance, - SettingsInterface, - SettingsData, - SettingsDataCategory, - SettingsAbout, +import androidx.navigation3.runtime.NavKey +import cn.super12138.todo.logic.database.TodoEntity +import kotlinx.serialization.Serializable - //SettingsAboutSpecial, - SettingsAboutLicence, - SettingsDev, - SettingsDevPadding +@Serializable +sealed class TodoScreen : NavKey { + @Serializable + data object Main : TodoScreen() + + @Serializable + data class Editor(val toDo: TodoEntity?) : TodoScreen() + + @Serializable + data object SettingsMain : TodoScreen() + + @Serializable + data object SettingsAppearance : TodoScreen() + + @Serializable + data object SettingsInterface : TodoScreen() + + @Serializable + data object SettingsData : TodoScreen() + + @Serializable + data object SettingsDataCategory : TodoScreen() + + @Serializable + data object SettingsAbout : TodoScreen() + + // @Serializable + // data object SettingsAboutSpecial : TodoScreen() + + @Serializable + data object SettingsAboutLicence : TodoScreen() + + @Serializable + data object SettingsDev : TodoScreen() + + @Serializable + data object SettingsDevPadding : TodoScreen() } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index 4b62680..9b40cc3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.editor import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.expandVertically @@ -43,6 +42,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.navigation3.ui.LocalNavAnimatedContentScope import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity @@ -69,11 +69,11 @@ fun TodoEditorPage( onSave: (TodoEntity) -> Unit, onDelete: () -> Unit, onNavigateUp: () -> Unit, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope + sharedTransitionScope: SharedTransitionScope ) { // TODO: 本页及其相关组件重组性能检查优化 val view = LocalView.current + val animatedVisibilityScope = LocalNavAnimatedContentScope.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val uiState = rememberEditorState(initialTodo = toDo) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt index 0513e79..99d9824 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/state/EditorState.kt @@ -74,7 +74,7 @@ class EditorState(val initialTodo: TodoEntity? = null) { * 保存状态的 Saver 对象,用于适配 rememberSaveable */ object Saver : androidx.compose.runtime.saveable.Saver { - override fun SaverScope.save(value: EditorState): Any? { + override fun SaverScope.save(value: EditorState): Any { return listOf( value.initialTodo?.id ?: 0, value.toDoContent, @@ -89,7 +89,7 @@ class EditorState(val initialTodo: TodoEntity? = null) { ) } - override fun restore(value: Any): EditorState? { + override fun restore(value: Any): EditorState { val list = value as List<*> val initialTodo = list[0] as? TodoEntity? return EditorState(initialTodo).apply { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 8ec35a1..caed89c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -1,7 +1,6 @@ package cn.super12138.todo.ui.pages.main import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Column @@ -31,9 +30,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.window.core.layout.WindowSizeClass import cn.super12138.todo.R import cn.super12138.todo.constants.Constants +import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar @@ -44,12 +45,13 @@ import cn.super12138.todo.ui.viewmodels.MainViewModel fun MainPage( modifier: Modifier = Modifier, viewModel: MainViewModel, - toTodoEditPage: () -> Unit, + toTodoEditPage: (TodoEntity?) -> Unit, toSettingsPage: () -> Unit, sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope, windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, ) { + val animatedVisibilityScope = LocalNavAnimatedContentScope.current + val toDos = viewModel.sortedTodos.collectAsState(initial = emptyList()) val selectedTodos = viewModel.selectedTodoIds.collectAsState() val showCompleted by DataStoreManager.showCompletedFlow.collectAsState(initial = Constants.PREF_SHOW_COMPLETED_DEFAULT) @@ -91,10 +93,7 @@ fun MainPage( ) }, expanded = expandedFab, - onClick = { - viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办 - toTodoEditPage() - }, + onClick = { toTodoEditPage(null) }, modifier = Modifier .sharedElement( sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), @@ -133,8 +132,7 @@ fun MainPage( if (inSelectedMode) { viewModel.toggleTodoSelection(item) } else { - viewModel.setEditTodoItem(item) - toTodoEditPage() + toTodoEditPage(item) } }, onItemLongClick = { viewModel.toggleTodoSelection(it) }, @@ -171,8 +169,7 @@ fun MainPage( if (inSelectedMode) { viewModel.toggleTodoSelection(item) } else { - viewModel.setEditTodoItem(item) - toTodoEditPage() + toTodoEditPage(item) } }, onItemLongClick = { viewModel.toggleTodoSelection(it) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index e726ec4..bc533ca 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.settings import android.widget.Toast import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index 2f9d3b9..4095f91 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -2,9 +2,7 @@ package cn.super12138.todo.ui.viewmodels import android.content.Context import android.net.Uri -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import cn.super12138.todo.TodoApp @@ -51,8 +49,6 @@ class MainViewModel : ViewModel() { } val showConfetti = mutableStateOf(false) - var selectedEditTodo by mutableStateOf(null) - private set // 多选逻辑参考:https://github.com/X1nto/Mauth private val _selectedTodoIds = MutableStateFlow(listOf()) @@ -82,10 +78,6 @@ class MainViewModel : ViewModel() { } }*/ - fun setEditTodoItem(toDo: TodoEntity?) { - selectedEditTodo = toDo - } - /** * 切换待办的选择状态 */ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14ef0fa..da7bd7f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,9 @@ composeBom = "2025.11.00" liveData = "1.9.4" icons = "1.7.8" room = "2.8.3" -navigation = "2.9.6" material3 = "1.5.0-alpha08" adaptive = "1.2.0" +nav3Core = "1.0.0-rc01" # AboutLibraries aboutLibsRelease = "13.1.0" aboutLibsReleasePlugin = "13.1.0" @@ -49,7 +49,6 @@ androidx-datastore-preferences = { group = "androidx.datastore", name = "datasto # Compose androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-animation = { group = "androidx.compose.animation", name = "animation" } -androidx-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android" } @@ -61,6 +60,8 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-material-icon-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "icons" } androidx-material-icon-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "icons" } +androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } +androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } # Room androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }