refactor: 优化导航栏的显示效果 & 为导航栏添加触感反馈
This commit is contained in:
parent
ddb02eb9a3
commit
2fbb6fd38c
10 changed files with 136 additions and 156 deletions
|
|
@ -38,7 +38,7 @@ android {
|
|||
applicationId = "cn.super12138.todo"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 969
|
||||
versionCode = 971
|
||||
versionName = "2.3.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldValue
|
||||
import androidx.compose.material3.adaptive.navigationsuite.rememberNavigationSuiteScaffoldState
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
|
|
@ -40,8 +43,12 @@ class MainActivity : ComponentActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val mainViewModel: MainViewModel = viewModel()
|
||||
val mainBackStack = mainViewModel.mainBackStack
|
||||
|
||||
val showConfetti = mainViewModel.showConfetti
|
||||
|
||||
val mainBackStack = mainViewModel.mainBackStack
|
||||
val navigationScaffoldState = rememberNavigationSuiteScaffoldState()
|
||||
|
||||
// 主题
|
||||
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
|
||||
val paletteStyle by DataStoreManager.paletteStyleFlow.collectAsState(initial = Constants.PREF_PALETTE_STYLE_DEFAULT)
|
||||
|
|
@ -81,14 +88,30 @@ class MainActivity : ComponentActivity() {
|
|||
VibrationUtils.setEnabled(hapticFeedback)
|
||||
}
|
||||
|
||||
// 当BackStack出现非顶层路由时,隐藏底部导航栏
|
||||
LaunchedEffect(mainBackStack.backStack.lastOrNull()) {
|
||||
val isTopLevel =
|
||||
mainBackStack.backStack.lastOrNull() in TodoDestinations.entries.map { it.route }
|
||||
if (isTopLevel) {
|
||||
if (navigationScaffoldState.currentValue != NavigationSuiteScaffoldValue.Visible) navigationScaffoldState.show()
|
||||
} else {
|
||||
if (navigationScaffoldState.currentValue != NavigationSuiteScaffoldValue.Hidden) navigationScaffoldState.hide()
|
||||
}
|
||||
}
|
||||
|
||||
ToDoTheme(
|
||||
darkTheme = darkTheme,
|
||||
style = PaletteStyle.fromId(paletteStyle),
|
||||
contrastLevel = contrastLevel.toDouble(),
|
||||
dynamicColor = dynamicColor
|
||||
) {
|
||||
Surface(color = TodoDefaults.BackgroundColor) {
|
||||
Surface(
|
||||
color = TodoDefaults.BackgroundColor,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
val view = LocalView.current
|
||||
NavigationSuiteScaffold(
|
||||
state = navigationScaffoldState,
|
||||
navigationSuiteItems = {
|
||||
TodoDestinations.entries.forEach { destination ->
|
||||
val selected = destination.route == mainBackStack.topLevelKey
|
||||
|
|
@ -110,11 +133,15 @@ class MainActivity : ComponentActivity() {
|
|||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
selected = selected,
|
||||
onClick = { mainBackStack.addTopLevel(destination.route) }
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
mainBackStack.addTopLevel(destination.route)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
containerColor = TodoDefaults.BackgroundColor
|
||||
containerColor = TodoDefaults.BackgroundColor,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TopNavigation(
|
||||
backStack = mainBackStack,
|
||||
|
|
@ -122,7 +149,10 @@ class MainActivity : ComponentActivity() {
|
|||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
Konfetti(state = showConfetti)
|
||||
Konfetti(
|
||||
state = showConfetti,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.super12138.todo.ui.components
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
|
@ -45,9 +44,9 @@ fun Konfetti(
|
|||
}
|
||||
}
|
||||
KonfettiView(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(modifier),
|
||||
modifier = modifier
|
||||
/*.fillMaxSize()
|
||||
.then(modifier)*/,
|
||||
parties = remember { particles(primary.toArgb()) },
|
||||
updateListener = listener
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAppearance
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsData
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsDataCategory
|
||||
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.materialSharedAxisX
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
private const val INITIAL_OFFSET_FACTOR = 0.10f
|
||||
|
||||
@Composable
|
||||
fun SettingsNavigation(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
viewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
fun onBack() {
|
||||
backStack.removeLastOrNull()
|
||||
}
|
||||
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = ::onBack,
|
||||
transitionSpec = {
|
||||
materialSharedAxisX(
|
||||
initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
popTransitionSpec = {
|
||||
materialSharedAxisX(
|
||||
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
entryProvider = entryProvider {
|
||||
entry<TodoScreen.Settings.Main> {
|
||||
SettingsMain(
|
||||
toAppearancePage = { backStack.add(TodoScreen.Settings.Appearance) },
|
||||
toAboutPage = { backStack.add(TodoScreen.Settings.About) },
|
||||
toInterfacePage = { backStack.add(TodoScreen.Settings.Interface) },
|
||||
toDataPage = { backStack.add(TodoScreen.Settings.Data) },
|
||||
)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Appearance> {
|
||||
SettingsAppearance(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Interface> {
|
||||
SettingsInterface(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Data> {
|
||||
SettingsData(
|
||||
viewModel = viewModel,
|
||||
toCategoryManager = { backStack.add(TodoScreen.Settings.DataCategory) },
|
||||
onNavigateUp = ::onBack
|
||||
)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.DataCategory> {
|
||||
SettingsDataCategory(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.About> {
|
||||
SettingsAbout(
|
||||
//toSpecialPage = { backStack.add(TodoScreen.Settings.AboutSpecial) },
|
||||
toLicencePage = { backStack.add(TodoScreen.Settings.AboutLicence) },
|
||||
toDevPage = { backStack.add(TodoScreen.Settings.DeveloperOptions) },
|
||||
onNavigateUp = ::onBack,
|
||||
)
|
||||
}
|
||||
|
||||
/*entry<TodoScreen.Settings.AboutSpecial> {
|
||||
SettingsAboutSpecial(viewModel = viewModel)
|
||||
}*/
|
||||
|
||||
entry<TodoScreen.Settings.AboutLicence> {
|
||||
SettingsAboutLicence(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.DeveloperOptions> {
|
||||
SettingsDeveloperOptions(
|
||||
toPaddingPage = { backStack.add(TodoScreen.Settings.DeveloperOptionsPadding) },
|
||||
onNavigateUp = ::onBack
|
||||
)
|
||||
}
|
||||
entry<TodoScreen.Settings.DeveloperOptionsPadding> {
|
||||
SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -10,13 +10,39 @@ import androidx.navigation3.ui.NavDisplay
|
|||
import cn.super12138.todo.ui.pages.editor.TodoAddPage
|
||||
import cn.super12138.todo.ui.pages.editor.TodoEditPage
|
||||
import cn.super12138.todo.ui.pages.overview.OverviewPage
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAppearance
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsData
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsDataCategory
|
||||
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.pages.tasks.TasksPage
|
||||
import cn.super12138.todo.ui.theme.fadeThrough
|
||||
import cn.super12138.todo.ui.theme.materialSharedAxisX
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
/**
|
||||
* 来自:https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/transition/MaterialFadeThrough.java#L33
|
||||
*/
|
||||
|
||||
private const val INITIAL_OFFSET_FACTOR = 0.10f
|
||||
|
||||
private fun settingsTransition() = NavDisplay.transitionSpec {
|
||||
materialSharedAxisX(
|
||||
initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
} + NavDisplay.popTransitionSpec {
|
||||
materialSharedAxisX(
|
||||
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
} + NavDisplay.predictivePopTransitionSpec {
|
||||
materialSharedAxisX(
|
||||
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
|
|
@ -28,29 +54,6 @@ fun TopNavigation(
|
|||
fun onBack() {
|
||||
backStack.removeLast()
|
||||
}
|
||||
/*
|
||||
* NavDisplay.transitionSpec {
|
||||
* // Slide new content up, keeping the old content in place underneath
|
||||
* slideInVertically(
|
||||
* initialOffsetY = { it / 5 },
|
||||
* animationSpec = tween(200)
|
||||
* ) + fadeIn(tween(200)) togetherWith ExitTransition.KeepUntilTransitionsFinished
|
||||
* } + NavDisplay.popTransitionSpec {
|
||||
* // Slide old content down, revealing the new content in place underneath
|
||||
* EnterTransition.None togetherWith
|
||||
* slideOutVertically(
|
||||
* targetOffsetY = { it / 5 },
|
||||
* animationSpec = tween(200)
|
||||
* ) + fadeOut(tween(200))
|
||||
* } + NavDisplay.predictivePopTransitionSpec {
|
||||
* // Slide old content down, revealing the new content in place underneath
|
||||
* EnterTransition.None togetherWith
|
||||
* slideOutVertically(
|
||||
* targetOffsetY = { it / 5 },
|
||||
* animationSpec = tween(200)
|
||||
* ) + fadeOut(tween(200))
|
||||
* }
|
||||
*/
|
||||
|
||||
// val veilColor = MaterialTheme.colorScheme.surfaceDim
|
||||
SharedTransitionLayout {
|
||||
|
|
@ -66,15 +69,12 @@ fun TopNavigation(
|
|||
predictivePopTransitionSpec = {
|
||||
unveilIn(initialColor = veilColor) togetherWith fadeOut()
|
||||
},*/
|
||||
transitionSpec = {
|
||||
fadeThrough()
|
||||
},
|
||||
popTransitionSpec = {
|
||||
fadeThrough()
|
||||
},
|
||||
predictivePopTransitionSpec = {
|
||||
fadeThrough()
|
||||
},
|
||||
/**
|
||||
* 来自:https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/transition/MaterialFadeThrough.java#L33
|
||||
*/
|
||||
transitionSpec = { fadeThrough() },
|
||||
popTransitionSpec = { fadeThrough() },
|
||||
predictivePopTransitionSpec = { fadeThrough() },
|
||||
entryProvider = entryProvider {
|
||||
entry<TodoScreen.Overview> {
|
||||
OverviewPage(viewModel = viewModel)
|
||||
|
|
@ -118,11 +118,60 @@ fun TopNavigation(
|
|||
}
|
||||
|
||||
entry<TodoScreen.Settings.Main> {
|
||||
SettingsNavigation(
|
||||
backStack = viewModel.settingsBackStack,
|
||||
viewModel = viewModel
|
||||
SettingsMain(
|
||||
toAppearancePage = { backStack.add(TodoScreen.Settings.Appearance) },
|
||||
toAboutPage = { backStack.add(TodoScreen.Settings.About) },
|
||||
toInterfacePage = { backStack.add(TodoScreen.Settings.Interface) },
|
||||
toDataPage = { backStack.add(TodoScreen.Settings.Data) },
|
||||
)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Appearance>(metadata = settingsTransition()) {
|
||||
SettingsAppearance(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Interface>(metadata = settingsTransition()) {
|
||||
SettingsInterface(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.Data>(metadata = settingsTransition()) {
|
||||
SettingsData(
|
||||
viewModel = viewModel,
|
||||
toCategoryManager = { backStack.add(TodoScreen.Settings.DataCategory) },
|
||||
onNavigateUp = ::onBack
|
||||
)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.DataCategory>(metadata = settingsTransition()) {
|
||||
SettingsDataCategory(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.About>(metadata = settingsTransition()) {
|
||||
SettingsAbout(
|
||||
//toSpecialPage = { backStack.add(TodoScreen.Settings.AboutSpecial) },
|
||||
toLicencePage = { backStack.add(TodoScreen.Settings.AboutLicence) },
|
||||
toDevPage = { backStack.add(TodoScreen.Settings.DeveloperOptions) },
|
||||
onNavigateUp = ::onBack,
|
||||
)
|
||||
}
|
||||
|
||||
/*entry<TodoScreen.Settings.AboutSpecial>(metadata = settingsTransition()) {
|
||||
SettingsAboutSpecial(viewModel = viewModel)
|
||||
}*/
|
||||
|
||||
entry<TodoScreen.Settings.AboutLicence>(metadata = settingsTransition()) {
|
||||
SettingsAboutLicence(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
||||
entry<TodoScreen.Settings.DeveloperOptions>(metadata = settingsTransition()) {
|
||||
SettingsDeveloperOptions(
|
||||
toPaddingPage = { backStack.add(TodoScreen.Settings.DeveloperOptionsPadding) },
|
||||
onNavigateUp = ::onBack
|
||||
)
|
||||
}
|
||||
entry<TodoScreen.Settings.DeveloperOptionsPadding>(metadata = settingsTransition()) {
|
||||
SettingsDeveloperOptionsPadding(onNavigateUp = ::onBack)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ fun TodoDueDateChooser(
|
|||
},
|
||||
shapes = ButtonDefaults.shapes()
|
||||
) {
|
||||
Text("清除")
|
||||
Text(stringResource(R.string.action_clear))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ import cn.super12138.todo.R
|
|||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
|
||||
class EditorState(val initialTodo: TodoEntity? = null) {
|
||||
/* companion object{
|
||||
fun Saver() =
|
||||
androidx.compose.runtime.saveable.Saver<EditorState, TodoEntity>(
|
||||
save = { it.initialTodo },
|
||||
restore = { EditorState(it) },
|
||||
)
|
||||
}*/
|
||||
var toDoContent by mutableStateOf(initialTodo?.content ?: "")
|
||||
var isErrorContent by mutableStateOf(false)
|
||||
var selectedCategoryIndex by mutableIntStateOf(-1)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.net.Uri
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import cn.super12138.todo.TodoApp
|
||||
import cn.super12138.todo.constants.Constants
|
||||
|
|
@ -37,7 +36,6 @@ import java.util.zip.ZipOutputStream
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class MainViewModel : ViewModel() {
|
||||
val mainBackStack = TopLevelBackStack<NavKey>(TodoScreen.Overview)
|
||||
val settingsBackStack = NavBackStack<NavKey>(TodoScreen.Settings.Main)
|
||||
|
||||
// 待办
|
||||
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||
|
|
|
|||
|
|
@ -117,4 +117,5 @@
|
|||
<string name="page_tasks">待办</string>
|
||||
<string name="page_overview">概览</string>
|
||||
<string name="tip_short_category">尽量使用简短的分类名称</string>
|
||||
<string name="action_clear">清除</string>
|
||||
</resources>
|
||||
|
|
@ -118,4 +118,5 @@
|
|||
<string name="page_tasks">Tasks</string>
|
||||
<string name="page_overview">Overview</string>
|
||||
<string name="tip_short_category">Keep category names concise</string>
|
||||
<string name="action_clear">Clear</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue