diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 13fac07..e7f7195 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -4,6 +4,8 @@ object Constants { const val DEVELOPER_GITHUB = "https://github.com/Super12138/" const val GITHUB_REPO = "https://github.com/Super12138/ToDo/" + const val KEY_TODO_FAB_TRANSITION = "todo_fab" + const val SP_NAME = "cn.super12138.todo_preferences" const val PREF_DYNAMIC_COLOR = "dynamic_color" 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 277c3d7..e88029b 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 @@ -1,5 +1,7 @@ package cn.super12138.todo.ui.navigation +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController @@ -19,6 +21,7 @@ import cn.super12138.todo.ui.viewmodels.MainViewModel private const val INITIAL_OFFSET_FACTOR = 0.10f +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun TodoNavigation( navController: NavHostController = rememberNavController(), @@ -26,95 +29,101 @@ fun TodoNavigation( viewModel: MainViewModel, modifier: Modifier = Modifier ) { - NavHost( - navController = navController, - startDestination = startDestination, - enterTransition = { - materialSharedAxisXIn( - initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } - ) - }, - exitTransition = { - materialSharedAxisXOut( - targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) - }, - popEnterTransition = { - materialSharedAxisXIn( - initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } - ) - }, - 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) } - ) - } + SharedTransitionLayout { + NavHost( + navController = navController, + startDestination = startDestination, + enterTransition = { + materialSharedAxisXIn( + initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() } + ) + }, + exitTransition = { + materialSharedAxisXOut( + targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } + ) + }, + popEnterTransition = { + materialSharedAxisXIn( + initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() } + ) + }, + 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) - viewModel.setEditTodoItem(null) - navController.navigateUp() - }, - onDelete = { - if (viewModel.selectedEditTodo !== null) { - viewModel.deleteTodo(viewModel.selectedEditTodo!!) + composable(TodoScreen.TodoEditor.name) { + TodoEditorPage( + toDo = viewModel.selectedEditTodo, + onSave = { + viewModel.addTodo(it) viewModel.setEditTodoItem(null) - } - navController.navigateUp() - }, - onNavigateUp = { - navController.navigateUp() - viewModel.setEditTodoItem(null) - } - ) - } + navController.navigateUp() + }, + onDelete = { + if (viewModel.selectedEditTodo !== null) { + viewModel.deleteTodo(viewModel.selectedEditTodo!!) + viewModel.setEditTodoItem(null) + } + navController.navigateUp() + }, + onNavigateUp = { + navController.navigateUp() + viewModel.setEditTodoItem(null) + }, + 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) }, - onNavigateUp = { navController.navigateUp() }, - ) - } + composable(TodoScreen.SettingsMain.name) { + SettingsMain( + toAppearancePage = { navController.navigate(TodoScreen.SettingsAppearance.name) }, + toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }, + toInterfacePage = { navController.navigate(TodoScreen.SettingsInterface.name) }, + onNavigateUp = { navController.navigateUp() }, + ) + } - composable(TodoScreen.SettingsAppearance.name) { - SettingsAppearance( - viewModel = viewModel, - onNavigateUp = { navController.navigateUp() } - ) - } + composable(TodoScreen.SettingsAppearance.name) { + SettingsAppearance( + viewModel = viewModel, + onNavigateUp = { navController.navigateUp() } + ) + } - composable(TodoScreen.SettingsInterface.name) { - SettingsInterface( - viewModel = viewModel, - onNavigateUp = { navController.navigateUp() } - ) - } + composable(TodoScreen.SettingsInterface.name) { + SettingsInterface( + viewModel = viewModel, + onNavigateUp = { navController.navigateUp() } + ) + } - composable(TodoScreen.SettingsAbout.name) { - SettingsAbout( - onNavigateUp = { navController.navigateUp() }, - toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) } - ) - } + composable(TodoScreen.SettingsAbout.name) { + SettingsAbout( + onNavigateUp = { navController.navigateUp() }, + toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) } + ) + } - composable(TodoScreen.SettingsAboutLicence.name) { - SettingsAboutLicence( - onNavigateUp = { navController.navigateUp() } - ) + composable(TodoScreen.SettingsAboutLicence.name) { + SettingsAboutLicence( + onNavigateUp = { navController.navigateUp() } + ) + } } } } \ 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 6bf0e51..070d70f 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,6 +2,9 @@ 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.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -43,6 +46,7 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import cn.super12138.todo.R +import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.logic.model.Subjects @@ -51,13 +55,15 @@ import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.LargeTopAppBarScaffold -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable fun TodoEditorPage( toDo: TodoEntity? = null, onSave: (TodoEntity) -> Unit, onDelete: () -> Unit, onNavigateUp: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, modifier: Modifier = Modifier ) { BackHandler { @@ -75,48 +81,54 @@ fun TodoEditorPage( title = stringResource(if (toDo != null) R.string.title_edit_task else R.string.action_add_task), scrollBehavior = scrollBehavior, floatingActionButton = { - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { - if (toDo !== null) { + with(sharedTransitionScope) { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + if (toDo !== null) { + AnimatedExtendedFloatingActionButton( + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(R.string.action_delete) + ) + }, + text = { Text(stringResource(R.string.action_delete)) }, + expanded = true, + containerColor = MaterialTheme.colorScheme.errorContainer, + onClick = onDelete + ) + } AnimatedExtendedFloatingActionButton( icon = { Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = stringResource(R.string.action_delete) + imageVector = Icons.Outlined.Save, + contentDescription = stringResource(R.string.action_save) ) }, - text = { Text(stringResource(R.string.action_delete)) }, + text = { Text(stringResource(R.string.action_save)) }, expanded = true, - containerColor = MaterialTheme.colorScheme.errorContainer, - onClick = onDelete + onClick = { + if (text.trim().isEmpty()) { + isError = true + return@AnimatedExtendedFloatingActionButton + } + + isError = false + onSave( + TodoEntity( + content = text, + subject = selectedItemIndex, + isCompleted = toDo?.isCompleted ?: false, + priority = sliderPosition, + id = toDo?.id ?: 0 + ) + ) + }, + modifier = Modifier.sharedElement( + state = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), + animatedVisibilityScope = animatedVisibilityScope + ) ) } - AnimatedExtendedFloatingActionButton( - icon = { - Icon( - imageVector = Icons.Outlined.Save, - contentDescription = stringResource(R.string.action_save) - ) - }, - text = { Text(stringResource(R.string.action_save)) }, - expanded = true, - onClick = { - if (text.trim().isEmpty()) { - isError = true - return@AnimatedExtendedFloatingActionButton - } - - isError = false - onSave( - TodoEntity( - content = text, - subject = selectedItemIndex, - isCompleted = toDo?.isCompleted ?: false, - priority = sliderPosition, - id = toDo?.id ?: 0 - ) - ) - } - ) } }, onBack = onNavigateUp, 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 b439844..27e41a3 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 @@ -2,6 +2,9 @@ package cn.super12138.todo.ui.pages.main 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.expandIn import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -27,17 +30,21 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.window.core.layout.WindowWidthSizeClass +import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.ui.pages.main.components.TodoDeleteConfirmDialog import cn.super12138.todo.ui.pages.main.components.TodoFAB import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.viewmodels.MainViewModel +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun MainPage( viewModel: MainViewModel, toTodoEditPage: () -> Unit, toSettingsPage: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, modifier: Modifier = Modifier ) { val toDoList = viewModel.toDos.collectAsState(initial = emptyList()) @@ -80,15 +87,21 @@ fun MainPage( ) }, floatingActionButton = { - AnimatedVisibility( - visible = isSelectedIdsEmpty, - enter = fadeIn() + expandIn(), - exit = shrinkOut() + fadeOut() - ) { - TodoFAB( - expanded = isExpanded, - onClick = toTodoEditPage - ) + with(sharedTransitionScope) { + AnimatedVisibility( + visible = isSelectedIdsEmpty, + enter = fadeIn() + expandIn(), + exit = shrinkOut() + fadeOut() + ) { + TodoFAB( + expanded = isExpanded, + onClick = toTodoEditPage, + modifier = Modifier.sharedElement( + state = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), + animatedVisibilityScope = animatedVisibilityScope + ) + ) + } } }, contentWindowInsets = WindowInsets.safeContent.exclude(WindowInsets.ime), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoFAB.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoFAB.kt index d5a7535..c9faf5d 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoFAB.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoFAB.kt @@ -9,6 +9,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.R import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton @@ -33,7 +34,11 @@ fun TodoFAB( ) }, text = { - Text(stringResource(R.string.action_add_task)) + Text( + text = stringResource(R.string.action_add_task), + maxLines = 1, + overflow = TextOverflow.Clip + ) }, expanded = expanded, elevation = elevation,