feat: 使用规范化布局(导航方式1)

This commit is contained in:
Super12138 2026-01-24 10:36:03 +08:00
parent 562293fd17
commit a6e12f4981
47 changed files with 867 additions and 767 deletions

View file

@ -0,0 +1,19 @@
package cn.super12138.todo.logic.model
import androidx.annotation.StringRes
import cn.super12138.todo.R
enum class ContrastLevel(
val value: Float,
@StringRes val nameRes: Int
) {
VeryLow(value = -1f, nameRes = R.string.contrast_very_low),
Low(value = -0.5f, nameRes = R.string.contrast_low),
Default(value = 0f, nameRes = R.string.contrast_default),
Medium(value = 0.5f, nameRes = R.string.contrast_high),
High(value = 1f, nameRes = R.string.contrast_very_high);
companion object {
fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default
}
}

View file

@ -0,0 +1,19 @@
package cn.super12138.todo.logic.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import cn.super12138.todo.R
enum class DarkMode(
val id: Int,
@DrawableRes val iconRes: Int,
@StringRes val nameRes: Int
) {
FollowSystem(id = -1, iconRes = R.drawable.ic_lightbulb_2, nameRes = R.string.dark_mode_system),
Light(id = 1, iconRes = R.drawable.ic_light_mode, nameRes = R.string.dark_mode_light),
Dark(id = 2, iconRes = R.drawable.ic_dark_mode, nameRes = R.string.dark_mode_dark);
companion object {
fun fromId(id: Int) = entries.find { it.id == id } ?: FollowSystem
}
}

View file

@ -0,0 +1,23 @@
package cn.super12138.todo.logic.model
import androidx.annotation.StringRes
import cn.super12138.todo.R
enum class PaletteStyle(
val id: Int,
@StringRes val nameRes: Int
) {
TonalSpot(id = 1, nameRes = R.string.palette_tonal_spot),
Neutral(id = 2, nameRes = R.string.palette_neutral),
Vibrant(id = 3, nameRes = R.string.palette_vibrant),
Expressive(id = 4, nameRes = R.string.palette_expressive),
Rainbow(id = 5, nameRes = R.string.palette_rainbow),
FruitSalad(id = 6, nameRes = R.string.palette_fruit_salad),
Monochrome(id = 7, nameRes = R.string.palette_monochrome),
Fidelity(id = 8, nameRes = R.string.palette_fidelity),
Content(id = 9, nameRes = R.string.palette_content);
companion object {
fun fromId(id: Int) = entries.firstOrNull { it.id == id } ?: TonalSpot
}
}

View file

@ -1,25 +1,17 @@
package cn.super12138.todo.logic.model package cn.super12138.todo.logic.model
import android.content.Context import androidx.annotation.StringRes
import cn.super12138.todo.R import cn.super12138.todo.R
enum class Priority(val value: Float) { enum class Priority(
Urgent(2f), val value: Float,
Important(1f), @StringRes val nameRes: Int
Default(0f), ) {
NotImportant(-1f), Urgent(value = 2f, nameRes = R.string.priority_urgent),
NotUrgent(-2f); Important(value = 1f, nameRes = R.string.priority_important),
Default(value = 0f, nameRes = R.string.priority_default),
fun displayName(context: Context): String { NotImportant(value = -1f, nameRes = R.string.priority_not_important),
val resId = when (this) { NotUrgent(value = -2f, nameRes = R.string.priority_not_urgent);
Urgent -> R.string.priority_urgent
Important -> R.string.priority_important
Default -> R.string.priority_default
NotImportant -> R.string.priority_not_important
NotUrgent -> R.string.priority_not_urgent
}
return context.getString(resId)
}
companion object { companion object {
fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default

View file

@ -1,38 +1,29 @@
package cn.super12138.todo.logic.model package cn.super12138.todo.logic.model
import android.content.Context import androidx.annotation.StringRes
import cn.super12138.todo.R import cn.super12138.todo.R
enum class SortingMethod(val id: Int) { enum class SortingMethod(
val id: Int,
@StringRes val nameRes: Int
) {
// 按添加先后顺序 // 按添加先后顺序
Sequential(1), Sequential(id = 1, nameRes = R.string.sorting_sequential),
// 按学科 // 按学科
Category(2), Category(id = 2, nameRes = R.string.sorting_category),
// 按优先级 // 按优先级
Priority(3), Priority(id = 3, nameRes = R.string.sorting_priority),
// 按完成情况 // 按完成情况
Completion(4), Completion(id = 4, nameRes = R.string.sorting_completion),
// 按字母升序 // 按字母升序
AlphabeticalAscending(5), AlphabeticalAscending(id = 5, nameRes = R.string.sorting_alphabetical_ascending),
// 按字母降序 // 按字母降序
AlphabeticalDescending(6); AlphabeticalDescending(id = 6, nameRes = R.string.sorting_alphabetical_descending);
fun getDisplayName(context: Context): String {
val resId = when (this) {
Sequential -> R.string.sorting_sequential
Category -> R.string.sorting_category
Priority -> R.string.sorting_priority
Completion -> R.string.sorting_completion
AlphabeticalAscending -> R.string.sorting_alphabetical_ascending
AlphabeticalDescending -> R.string.sorting_alphabetical_descending
}
return context.getString(resId)
}
companion object { companion object {
fun fromId(id: Int) = entries.find { it.id == id } ?: Sequential fun fromId(id: Int) = entries.find { it.id == id } ?: Sequential

View file

@ -1,7 +1,6 @@
package cn.super12138.todo.ui package cn.super12138.todo.ui
import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -58,10 +57,10 @@ object TodoDefaults {
val ScreenContainerShape: Shape val ScreenContainerShape: Shape
@Composable @Composable
get() = MaterialTheme.shapes.large.copy( get() = MaterialTheme.shapes.large/*.copy(
bottomStart = ZeroCornerSize, bottomStart = ZeroCornerSize,
bottomEnd = ZeroCornerSize bottomEnd = ZeroCornerSize
) )*/
val ContainerColor: Color val ContainerColor: Color
@Composable @Composable

View file

@ -1,10 +1,8 @@
package cn.super12138.todo.ui.activities package cn.super12138.todo.ui.activities
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -16,11 +14,12 @@ import androidx.core.view.WindowCompat
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.DarkMode
import cn.super12138.todo.logic.model.PaletteStyle
import cn.super12138.todo.ui.pages.crash.CrashPage import cn.super12138.todo.ui.pages.crash.CrashPage
import cn.super12138.todo.ui.theme.DarkMode
import cn.super12138.todo.ui.theme.PaletteStyle
import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.ui.theme.ToDoTheme
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
import cn.super12138.todo.utils.configureEdgeToEdge
class CrashActivity : ComponentActivity() { class CrashActivity : ComponentActivity() {
companion object { companion object {
@ -32,10 +31,7 @@ class CrashActivity : ComponentActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge() configureEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val crashLogs = intent.getStringExtra("crash_logs") val crashLogs = intent.getStringExtra("crash_logs")

View file

@ -1,42 +1,45 @@
package cn.super12138.todo.ui.activities package cn.super12138.todo.ui.activities
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.DarkMode
import cn.super12138.todo.logic.model.PaletteStyle
import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.Konfetti import cn.super12138.todo.ui.components.Konfetti
import cn.super12138.todo.ui.navigation.TodoDestinations
import cn.super12138.todo.ui.navigation.TodoNavigation import cn.super12138.todo.ui.navigation.TodoNavigation
import cn.super12138.todo.ui.theme.DarkMode
import cn.super12138.todo.ui.theme.PaletteStyle
import cn.super12138.todo.ui.theme.ToDoTheme import cn.super12138.todo.ui.theme.ToDoTheme
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
import cn.super12138.todo.utils.configureEdgeToEdge
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen() installSplashScreen()
enableEdgeToEdge() configureEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
val mainViewModel: MainViewModel = viewModel() val mainViewModel: MainViewModel = viewModel()
val navigationBackStack = mainViewModel.topLevelBackStack
val showConfetti = mainViewModel.showConfetti val showConfetti = mainViewModel.showConfetti
// 主题 // 主题
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT) val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
@ -84,10 +87,33 @@ class MainActivity : ComponentActivity() {
dynamicColor = dynamicColor dynamicColor = dynamicColor
) { ) {
Surface(color = TodoDefaults.BackgroundColor) { Surface(color = TodoDefaults.BackgroundColor) {
TodoNavigation( NavigationSuiteScaffold(
viewModel = mainViewModel, navigationSuiteItems = {
modifier = Modifier.fillMaxSize() TodoDestinations.entries.forEach {
) val selected = it.route == navigationBackStack.topLevelKey
item(
icon = {
Icon(
painter = if (selected) painterResource(it.selectedIcon) else painterResource(
it.icon
),
contentDescription = null
)
},
label = { Text(stringResource(it.label)) },
selected = selected,
onClick = { navigationBackStack.addTopLevel(it.route) }
)
}
},
containerColor = TodoDefaults.BackgroundColor
) {
TodoNavigation(
backStack = navigationBackStack,
viewModel = mainViewModel,
modifier = Modifier.fillMaxSize()
)
}
Konfetti(state = showConfetti) Konfetti(state = showConfetti)
} }
} }

View file

@ -1,7 +1,5 @@
package cn.super12138.todo.ui.components package cn.super12138.todo.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -20,6 +18,8 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -33,10 +33,9 @@ import cn.super12138.todo.utils.VibrationUtils
* * 内容默认由 Box 容器包裹实际使用时推荐配合 Column Row * * 内容默认由 Box 容器包裹实际使用时推荐配合 Column Row
* *
* @param title 标题文本 * @param title 标题文本
* @param onBack 当返回按钮被按下时的操作
* @param contentWindowInsets 内容边距通常用于将内容和系统状态栏等隔开可以使用 `WindowInsets.safeContent` * @param contentWindowInsets 内容边距通常用于将内容和系统状态栏等隔开可以使用 `WindowInsets.safeContent`
*/ */
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun TopAppBarScaffold( fun TopAppBarScaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -46,7 +45,7 @@ fun TopAppBarScaffold(
floatingActionButton: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit content: @Composable () -> Unit
) { ) {
val view = LocalView.current val view = LocalView.current
TopAppBarScaffold( TopAppBarScaffold(
@ -77,10 +76,37 @@ fun TopAppBarScaffold(
floatingActionButtonPosition = floatingActionButtonPosition, floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets, contentWindowInsets = contentWindowInsets,
modifier = modifier, modifier = modifier,
) { innerPadding -> content = content
//TODO: 应用圆角效果 )
Box { content(innerPadding) } }
}
@Composable
fun TopAppBarScaffold(
modifier: Modifier = Modifier,
title: String,
navigationIcon: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable () -> Unit
) {
TopAppBarScaffold(
title = {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = navigationIcon,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets,
modifier = modifier,
content = content
)
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -94,9 +120,9 @@ fun TopAppBarScaffold(
floatingActionButton: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit content: @Composable () -> Unit
) { ) {
Scaffold( TopAppBarScaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
TopAppBar( TopAppBar(
@ -112,8 +138,37 @@ fun TopAppBarScaffold(
floatingActionButton = floatingActionButton, floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition, floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets, contentWindowInsets = contentWindowInsets,
containerColor = TodoDefaults.BackgroundColor, content = content,
) { innerPadding -> )
Box { content(innerPadding) } }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBarScaffold(
modifier: Modifier = Modifier,
topBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
containerColor: Color = TodoDefaults.BackgroundColor,
screenShape: Shape = TodoDefaults.ScreenContainerShape,
content: @Composable () -> Unit
) = Scaffold(
modifier = modifier,
topBar = topBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
contentWindowInsets = contentWindowInsets,
containerColor = containerColor,
) { innerPadding ->
Surface(
modifier = Modifier
.padding(paddingValues = innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding),
color = containerColor,
shape = screenShape,
content = content
)
} }

View file

@ -0,0 +1,2 @@
package cn.super12138.todo.ui.navigation

View file

@ -0,0 +1,32 @@
package cn.super12138.todo.ui.navigation
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.navigation3.runtime.NavKey
enum class TodoDestinations(
val route: NavKey,
@StringRes val label: Int,
@DrawableRes val icon: Int,
@DrawableRes val selectedIcon: Int
) {
Overview(
route = TodoScreen.Overview,
label = cn.super12138.todo.R.string.page_overview,
icon = cn.super12138.todo.R.drawable.ic_view_kanban,
selectedIcon = cn.super12138.todo.R.drawable.ic_view_kanban_filled
),
Tasks(
route = TodoScreen.Tasks,
label = cn.super12138.todo.R.string.page_tasks,
icon = cn.super12138.todo.R.drawable.ic_ballot,
selectedIcon = cn.super12138.todo.R.drawable.ic_ballot_filled
),
Settings(
route = TodoScreen.Settings,
label = cn.super12138.todo.R.string.page_settings,
icon = cn.super12138.todo.R.drawable.ic_settings,
selectedIcon = cn.super12138.todo.R.drawable.ic_settings_filled
)
}

View file

@ -1,16 +1,23 @@
package cn.super12138.todo.ui.navigation package cn.super12138.todo.ui.navigation
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.NavDisplay
import cn.super12138.todo.ui.pages.editor.TodoEditorPage import cn.super12138.todo.ui.pages.editor.TodoEditorPage
import cn.super12138.todo.ui.pages.main.MainPage import cn.super12138.todo.ui.pages.main.MainPage
import cn.super12138.todo.ui.pages.overview.OverviewPage
import cn.super12138.todo.ui.pages.settings.SettingsAbout import cn.super12138.todo.ui.pages.settings.SettingsAbout
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
import cn.super12138.todo.ui.pages.settings.SettingsAppearance import cn.super12138.todo.ui.pages.settings.SettingsAppearance
@ -28,17 +35,39 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f
@OptIn(ExperimentalSharedTransitionApi::class) @OptIn(ExperimentalSharedTransitionApi::class)
@Composable @Composable
fun TodoNavigation( fun TodoNavigation(
backStack: TopLevelBackStack<NavKey>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
backStack: NavBackStack<NavKey> = rememberNavBackStack(TodoScreen.Main),
viewModel: MainViewModel viewModel: MainViewModel
) { ) {
fun onBack() { fun onBack() {
backStack.removeAt(backStack.lastIndex) backStack.removeLast()
}
val anim = 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))
} }
SharedTransitionLayout { SharedTransitionLayout {
NavDisplay( NavDisplay(
backStack = backStack, backStack = backStack.backStack,
onBack = ::onBack, onBack = ::onBack,
transitionSpec = { transitionSpec = {
materialSharedAxisX( materialSharedAxisX(
@ -50,16 +79,27 @@ fun TodoNavigation(
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }, initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() })
}, },
entryProvider = entryProvider { entryProvider = entryProvider {
entry<TodoScreen.Main> { entry<TodoScreen.Overview>(metadata = anim) {
OverviewPage()
}
entry<TodoScreen.Tasks>(metadata = anim) {
MainPage( MainPage(
viewModel = viewModel, viewModel = viewModel,
toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) }, toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) },
toSettingsPage = { backStack.add(TodoScreen.SettingsMain) },
sharedTransitionScope = this@SharedTransitionLayout sharedTransitionScope = this@SharedTransitionLayout
) )
} }
entry<TodoScreen.Settings>(metadata = anim) {
SettingsMain(
toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) },
toAboutPage = { backStack.add(TodoScreen.SettingsAbout) },
toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) },
toDataPage = { backStack.add(TodoScreen.SettingsData) },
)
}
entry<TodoScreen.Editor> { editorArgs -> entry<TodoScreen.Editor> { editorArgs ->
TodoEditorPage( TodoEditorPage(
toDo = editorArgs.toDo, toDo = editorArgs.toDo,
@ -82,16 +122,6 @@ fun TodoNavigation(
) )
} }
entry<TodoScreen.SettingsMain> {
SettingsMain(
toAppearancePage = { backStack.add(TodoScreen.SettingsAppearance) },
toAboutPage = { backStack.add(TodoScreen.SettingsAbout) },
toInterfacePage = { backStack.add(TodoScreen.SettingsInterface) },
toDataPage = { backStack.add(TodoScreen.SettingsData) },
onNavigateUp = ::onBack,
)
}
entry<TodoScreen.SettingsAppearance> { entry<TodoScreen.SettingsAppearance> {
SettingsAppearance(onNavigateUp = ::onBack) SettingsAppearance(onNavigateUp = ::onBack)
} }

View file

@ -7,13 +7,16 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
sealed class TodoScreen : NavKey { sealed class TodoScreen : NavKey {
@Serializable @Serializable
data object Main : TodoScreen() data object Overview : TodoScreen()
@Serializable
data object Tasks : TodoScreen()
@Serializable @Serializable
data class Editor(val toDo: TodoEntity?) : TodoScreen() data class Editor(val toDo: TodoEntity?) : TodoScreen()
@Serializable @Serializable
data object SettingsMain : TodoScreen() data object Settings : TodoScreen()
@Serializable @Serializable
data object SettingsAppearance : TodoScreen() data object SettingsAppearance : TodoScreen()
@ -41,4 +44,4 @@ sealed class TodoScreen : NavKey {
@Serializable @Serializable
data object SettingsDevPadding : TodoScreen() data object SettingsDevPadding : TodoScreen()
} }

View file

@ -0,0 +1,62 @@
package cn.super12138.todo.ui.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
@Composable
fun <T : Any> rememberTopLevelBackStack(startKey: T): TopLevelBackStack<T> =
remember { TopLevelBackStack(startKey = startKey) }
class TopLevelBackStack<T : Any>(startKey: T) {
// Maintain a stack for each top level route
private var topLevelStacks: LinkedHashMap<T, SnapshotStateList<T>> = linkedMapOf(
startKey to mutableStateListOf(startKey)
)
// Expose the current top level route for consumers
var topLevelKey: T by mutableStateOf(startKey)
private set
// Expose the back stack so it can be rendered by the NavDisplay
val backStack = mutableStateListOf(startKey)
private fun updateBackStack() =
backStack.apply {
clear()
addAll(topLevelStacks.flatMap { it.value })
}
fun addTopLevel(key: T) {
// If the top level doesn't exist, add it
if (topLevelStacks[key] == null) {
topLevelStacks.put(key, mutableStateListOf(key))
} else {
// Otherwise just move it to the end of the stacks
topLevelStacks.apply {
remove(key)?.let {
put(key, it)
}
}
}
topLevelKey = key
updateBackStack()
}
fun add(key: T) {
topLevelStacks[topLevelKey]?.add(key)
updateBackStack()
}
fun removeLast() {
val removedKey = topLevelStacks[topLevelKey]?.removeLastOrNull()
// If the removed key was a top level key, remove the associated top level stack
topLevelStacks.remove(removedKey)
topLevelKey = topLevelStacks.keys.last()
updateBackStack()
}
}

View file

@ -41,11 +41,10 @@ import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.ChipItem import cn.super12138.todo.ui.components.ChipItem
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
@ -159,13 +158,10 @@ fun TodoEditorPage(
}, },
onBack = ::checkModifiedBeforeBack, onBack = ::checkModifiedBeforeBack,
modifier = modifier modifier = modifier
) { innerPadding -> ) {
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(5.dp), verticalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier modifier = Modifier.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
.fillMaxSize()
) { ) {
item { item {
TodoContentTextField( TodoContentTextField(

View file

@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.LiveRegionMode import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.liveRegion
@ -35,7 +36,7 @@ fun TodoPrioritySlider(
val view = LocalView.current val view = LocalView.current
val context = LocalContext.current val context = LocalContext.current
val priorityName = remember { Priority.entries.map { it.displayName(context) } } val priorityName = Priority.entries.map { stringResource(it.nameRes) }
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
Slider( Slider(

View file

@ -1,86 +0,0 @@
package cn.super12138.todo.ui.pages.main
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
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.TextAlign
import androidx.compose.ui.unit.dp
import cn.super12138.todo.R
import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.model.Priority
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.pages.main.components.TodoCard
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ManagerFragment(
modifier: Modifier = Modifier,
state: LazyListState,
list: List<TodoEntity>,
onItemClick: (TodoEntity) -> Unit = {},
onItemLongClick: (TodoEntity) -> Unit = {},
onItemChecked: (TodoEntity) -> Unit = {},
selectedTodoIds: List<Int>
) {
LazyColumn(
state = state,
contentPadding = PaddingValues(
start = TodoDefaults.screenHorizontalPadding,
bottom = TodoDefaults.toDoCardHeight / 2,
end = TodoDefaults.screenHorizontalPadding
),
modifier = modifier
/*.clip(
MaterialTheme.shapes.extraLarge.copy(
bottomEnd = CornerSize(0.dp),
bottomStart = CornerSize(0.dp)
)
)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)*/
// verticalArrangement = Arrangement.spacedBy(10.dp)
) {
if (list.isEmpty()) {
item {
Text(
text = stringResource(R.string.tip_no_task),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
} else {
items(
items = list,
key = { it.id }
) { item ->
TodoCard(
// id = item.id,
content = item.content,
category = item.category,
completed = item.isCompleted,
priority = Priority.fromFloat(item.priority),
selected = selectedTodoIds.contains(item.id),
onCardClick = { onItemClick(item) },
onCardLongClick = { onItemLongClick(item) },
onChecked = { onItemChecked(item) },
modifier = Modifier
.padding(vertical = 5.dp)
.animateItem(
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec()
)
)
}
}
}
}

View file

@ -2,13 +2,18 @@ package cn.super12138.todo.ui.pages.main
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Scaffold import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -21,13 +26,19 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.LocalNavAnimatedContentScope
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.Priority
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.main.components.TodoCard
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
@ -37,9 +48,7 @@ fun MainPage(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: MainViewModel, viewModel: MainViewModel,
toTodoEditPage: (TodoEntity?) -> Unit, toTodoEditPage: (TodoEntity?) -> Unit,
toSettingsPage: () -> Unit,
sharedTransitionScope: SharedTransitionScope, sharedTransitionScope: SharedTransitionScope,
// windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
) { ) {
val animatedVisibilityScope = LocalNavAnimatedContentScope.current val animatedVisibilityScope = LocalNavAnimatedContentScope.current
@ -53,8 +62,8 @@ fun MainPage(
val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } } val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } }
val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } } val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } }
val toDoList by remember { derivedStateOf { toDos.value } } val toDoList by remember { derivedStateOf { toDos.value } }
val totalTasks by remember { derivedStateOf { toDoList.size } } /*val totalTasks by remember { derivedStateOf { toDoList.size } }
val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } } val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } }*/
val filteredTodoList = val filteredTodoList =
if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted } if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
@ -62,15 +71,14 @@ fun MainPage(
// 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效 // 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效
BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() } BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() }
Scaffold( TopAppBarScaffold(
topBar = { topBar = {
TodoTopAppBar( TodoTopAppBar(
selectedTodoIds = selectedTodoIds, selectedTodoIds = selectedTodoIds,
selectedMode = inSelectedMode, selectedMode = inSelectedMode,
onCancelSelect = { viewModel.clearAllTodoSelection() }, onCancelSelect = { viewModel.clearAllTodoSelection() },
onSelectAll = { viewModel.selectAllTodos() }, onSelectAll = { viewModel.selectAllTodos() },
onDeleteSelectedTodo = { showDeleteConfirmDialog = true }, onDeleteSelectedTodo = { showDeleteConfirmDialog = true }
toSettingsPage = toSettingsPage
) )
}, },
floatingActionButton = { floatingActionButton = {
@ -94,84 +102,68 @@ fun MainPage(
}, },
contentWindowInsets = WindowInsets(0, 0, 0, 0), contentWindowInsets = WindowInsets(0, 0, 0, 0),
modifier = modifier modifier = modifier
) { innerPadding -> ) {
/*val isMediumScreen = LazyColumn(
windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND) state = listState,
if (isMediumScreen) { verticalArrangement = Arrangement.spacedBy(5.dp),
Row( modifier = Modifier.fillMaxSize()
modifier = Modifier.padding(
top = innerPadding.calculateTopPadding(),
bottom = innerPadding.calculateBottomPadding()
)
) {
ProgressFragment(
totalTasks = totalTasks,
completedTasks = completedTasks,
modifier = Modifier
.weight(2f)
.fillMaxSize()
)
ManagerFragment(
state = listState,
list = filteredTodoList,
onItemClick = { item ->
if (inSelectedMode) {
viewModel.toggleTodoSelection(item)
} else {
toTodoEditPage(item)
}
},
onItemLongClick = { viewModel.toggleTodoSelection(it) },
onItemChecked = {
viewModel.updateTodo(it.copy(isCompleted = true))
viewModel.playConfetti()
},
selectedTodoIds = selectedTodoIds,
// sharedTransitionScope = sharedTransitionScope,
// animatedVisibilityScope = animatedVisibilityScope,
modifier = Modifier
.weight(3f)
.fillMaxSize()
)
}
} else {*/
Column(
modifier = Modifier.padding(
top = innerPadding.calculateTopPadding(),
bottom = innerPadding.calculateBottomPadding()
)
) { ) {
ProgressFragment( item {
totalTasks = totalTasks, Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding))
completedTasks = completedTasks, }
modifier = Modifier.fillMaxWidth()
) if (filteredTodoList.isEmpty()) {
ManagerFragment( item {
state = listState, Text(
list = filteredTodoList, text = stringResource(R.string.tip_no_task),
onItemClick = { item -> style = MaterialTheme.typography.titleMedium,
if (inSelectedMode) { textAlign = TextAlign.Center,
viewModel.toggleTodoSelection(item) modifier = Modifier.fillMaxWidth()
} else { )
toTodoEditPage(item) }
} } else {
}, items(
onItemLongClick = { viewModel.toggleTodoSelection(it) }, items = filteredTodoList,
onItemChecked = { key = { it.id }
viewModel.updateTodo(it.copy(isCompleted = true)) ) {
viewModel.playConfetti() TodoCard(
}, // id = item.id,
selectedTodoIds = selectedTodoIds, content = it.content,
modifier = Modifier.fillMaxWidth() category = it.category,
) completed = it.isCompleted,
priority = Priority.fromFloat(it.priority),
selected = selectedTodoIds.contains(it.id),
onCardClick = {
if (inSelectedMode) {
viewModel.toggleTodoSelection(it)
} else {
toTodoEditPage(it)
}
},
onCardLongClick = { viewModel.toggleTodoSelection(it) },
onChecked = {
viewModel.updateTodo(it.copy(isCompleted = true))
viewModel.playConfetti()
},
modifier = Modifier
.animateItem(
fadeInSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
placementSpec = MaterialTheme.motionScheme.defaultSpatialSpec(),
fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec()
)
)
}
item {
Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding))
}
}
} }
/*}*/
ConfirmDialog(
visible = showDeleteConfirmDialog,
iconRes = R.drawable.ic_delete,
text = stringResource(R.string.tip_delete_task, selectedTodoIds.size),
onConfirm = { viewModel.deleteSelectedTodo() },
onDismiss = { showDeleteConfirmDialog = false }
)
} }
ConfirmDialog(
visible = showDeleteConfirmDialog,
iconRes = R.drawable.ic_delete,
text = stringResource(R.string.tip_delete_task, selectedTodoIds.size),
onConfirm = { viewModel.deleteSelectedTodo() },
onDismiss = { showDeleteConfirmDialog = false }
)
} }

View file

@ -121,7 +121,7 @@ fun TodoCard(
modifier = Modifier.padding(start = 5.dp) modifier = Modifier.padding(start = 5.dp)
) { ) {
Text( Text(
text = priority.displayName(context), text = stringResource(priority.nameRes),
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
) )
} }

View file

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@ -30,6 +31,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -40,15 +42,14 @@ fun TodoTopAppBar(
onCancelSelect: () -> Unit, onCancelSelect: () -> Unit,
onSelectAll: () -> Unit, onSelectAll: () -> Unit,
onDeleteSelectedTodo: () -> Unit, onDeleteSelectedTodo: () -> Unit,
toSettingsPage: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val view = LocalView.current val view = LocalView.current
val animatedTopAppBarColors by animateColorAsState( val animatedTopAppBarColors by animateColorAsState(
targetValue = if (selectedMode) { targetValue = if (selectedMode) {
TopAppBarDefaults.topAppBarColors().scrolledContainerColor MaterialTheme.colorScheme.surfaceContainerHighest
} else { } else {
TopAppBarDefaults.topAppBarColors().containerColor TodoDefaults.BackgroundColor
} }
) )
@ -103,52 +104,29 @@ fun TodoTopAppBar(
} }
}, },
actions = { actions = {
AnimatedContent( AnimatedVisibility(visible = selectedMode) {
targetState = selectedMode, Row {
transitionSpec = {
(
fadeIn() + scaleIn(initialScale = 0.92f)
).togetherWith(
fadeOut(animationSpec = tween(90))
)
}
) { inSelectedMode ->
if (!inSelectedMode) {
IconButton( IconButton(
onClick = { onClick = {
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
toSettingsPage() onSelectAll()
} }
) { ) {
Icon( Icon(
painter = painterResource(R.drawable.ic_settings), painter = painterResource(R.drawable.ic_select_all),
contentDescription = stringResource(R.string.page_settings) contentDescription = stringResource(R.string.tip_select_all)
) )
} }
} else { IconButton(
Row { onClick = {
IconButton( VibrationUtils.performHapticFeedback(view)
onClick = { onDeleteSelectedTodo()
VibrationUtils.performHapticFeedback(view)
onSelectAll()
}
) {
Icon(
painter = painterResource(R.drawable.ic_select_all),
contentDescription = stringResource(R.string.tip_select_all)
)
}
IconButton(
onClick = {
VibrationUtils.performHapticFeedback(view)
onDeleteSelectedTodo()
}
) {
Icon(
painter = painterResource(R.drawable.ic_delete),
contentDescription = stringResource(R.string.action_delete)
)
} }
) {
Icon(
painter = painterResource(R.drawable.ic_delete),
contentDescription = stringResource(R.string.action_delete)
)
} }
} }
} }
@ -170,7 +148,6 @@ private fun TodoTopAppBarPreview() {
selectedMode = selectedMode, selectedMode = selectedMode,
onCancelSelect = { selectedMode = !selectedMode }, onCancelSelect = { selectedMode = !selectedMode },
onSelectAll = { }, onSelectAll = { },
onDeleteSelectedTodo = { }, onDeleteSelectedTodo = { }
toSettingsPage = { selectedMode = !selectedMode }
) )
} }

View file

@ -0,0 +1,27 @@
package cn.super12138.todo.ui.pages.overview
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R
import cn.super12138.todo.ui.components.TopAppBarScaffold
@Composable
fun OverviewPage(modifier: Modifier = Modifier) {
TopAppBarScaffold(
title = stringResource(R.string.page_overview),
modifier = modifier
) {
Row {
Card {
Icon(painter = painterResource(R.drawable.ic_ballot), contentDescription = null)
Text("总任务")
}
}
}
}

View file

@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.settings
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -19,7 +18,6 @@ import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.icons.GitHubIcon import cn.super12138.todo.ui.icons.GitHubIcon
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@ -39,7 +37,7 @@ fun SettingsAbout(
title = stringResource(R.string.pref_about), title = stringResource(R.string.pref_about),
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
val context = LocalContext.current val context = LocalContext.current
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
var clickCount by remember { mutableIntStateOf(0) } var clickCount by remember { mutableIntStateOf(0) }
@ -57,60 +55,58 @@ fun SettingsAbout(
} }
} }
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item {
item { SettingsItem(
SettingsItem( leadingIconRes = R.drawable.ic_numbers,
leadingIconRes = R.drawable.ic_numbers, title = stringResource(R.string.pref_app_version),
title = stringResource(R.string.pref_app_version), description = SystemUtils.getAppVersion(context),
description = SystemUtils.getAppVersion(context), onClick = {
onClick = { clickCount++
clickCount++ if (clickCount == 5) {
if (clickCount == 5) { if ((System.currentTimeMillis() % 2) == 0.toLong()) {
if ((System.currentTimeMillis() % 2) == 0.toLong()) { Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show() } else {
} else { Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
}
clickCount = 0
} }
}, clickCount = 0
topRounded = true }
) },
} topRounded = true
item { )
SettingsItem( }
leadingIconRes = R.drawable.ic_person_4, item {
title = stringResource(R.string.pref_developer), SettingsItem(
description = stringResource(R.string.developer_name), leadingIconRes = R.drawable.ic_person_4,
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) }, title = stringResource(R.string.pref_developer),
) description = stringResource(R.string.developer_name),
} onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) },
item { )
SettingsItem( }
leadingIcon = GitHubIcon, item {
title = stringResource(R.string.pref_view_on_github), SettingsItem(
description = stringResource(R.string.pref_view_on_github_desc), leadingIcon = GitHubIcon,
onClick = { uriHandler.openUri(Constants.GITHUB_REPO) } title = stringResource(R.string.pref_view_on_github),
) description = stringResource(R.string.pref_view_on_github_desc),
} onClick = { uriHandler.openUri(Constants.GITHUB_REPO) }
item { )
SettingsItem( }
leadingIconRes = R.drawable.ic_balance, item {
title = stringResource(R.string.pref_licence), SettingsItem(
description = stringResource(R.string.pref_licence_desc), leadingIconRes = R.drawable.ic_balance,
onClick = toLicencePage title = stringResource(R.string.pref_licence),
) description = stringResource(R.string.pref_licence_desc),
} onClick = toLicencePage
item { )
SettingsItem( }
leadingIconRes = R.drawable.ic_code_blocks, item {
title = stringResource(R.string.pref_developer_options), SettingsItem(
description = stringResource(R.string.pref_developer_options_desc), leadingIconRes = R.drawable.ic_code_blocks,
onClick = toDevPage, title = stringResource(R.string.pref_developer_options),
bottomRounded = true description = stringResource(R.string.pref_developer_options_desc),
) onClick = toDevPage,
} bottomRounded = true
)
} }
} }
} }

View file

@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
@ -30,7 +29,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.components.BasicDialog import cn.super12138.todo.ui.components.BasicDialog
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@ -53,103 +51,100 @@ fun SettingsAboutLicence(
title = stringResource(R.string.pref_licence), title = stringResource(R.string.pref_licence),
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier modifier = modifier
) { innerPadding -> ) {
SettingsContainer(Modifier.fillMaxSize()) {
itemsIndexed(
items = libraries?.libraries ?: listOf(),
key = { _, library -> library.artifactId }
) { index, library ->
var openDialog by rememberSaveable { mutableStateOf(false) }
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsItem(
SettingsContainer(Modifier.fillMaxSize()) { headlineContent = {
itemsIndexed( Row(
items = libraries?.libraries ?: listOf(), verticalAlignment = Alignment.CenterVertically,
key = { _, library -> library.artifactId } horizontalArrangement = Arrangement.SpaceBetween
) { index, library -> ) {
var openDialog by rememberSaveable { mutableStateOf(false) } Text(
text = library.name,
SettingsItem( modifier = Modifier.weight(1f),
headlineContent = { style = MaterialTheme.typography.titleLarge,
Row( maxLines = 1,
verticalAlignment = Alignment.CenterVertically, overflow = TextOverflow.Ellipsis
horizontalArrangement = Arrangement.SpaceBetween )
) { val version = library.artifactVersion
if (version != null) {
Text( Text(
text = library.name, text = version,
modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.titleLarge, textAlign = TextAlign.Center
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
val version = library.artifactVersion
if (version != null) {
Text(
text = version,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
}
}
val author = library.author
if (author.isNotBlank()) {
Text(
text = author,
style = MaterialTheme.typography.bodyMedium
) )
} }
}, }
supportingContent = { val author = library.author
if (library.licenses.isNotEmpty()) { if (author.isNotBlank()) {
FlowRow { Text(
library.licenses.forEach { text = author,
Badge(containerColor = MaterialTheme.colorScheme.primary) { style = MaterialTheme.typography.bodyMedium
Text( )
maxLines = 1, }
text = it.name, },
// style = MaterialTheme.typography.labelSmall, supportingContent = {
textAlign = TextAlign.Center, if (library.licenses.isNotEmpty()) {
overflow = TextOverflow.Ellipsis, FlowRow {
) library.licenses.forEach {
} Badge(containerColor = MaterialTheme.colorScheme.primary) {
Text(
maxLines = 1,
text = it.name,
// style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
)
} }
} }
} }
}, }
onClick = { },
val license = library.licenses.firstOrNull() onClick = {
if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { val license = library.licenses.firstOrNull()
openDialog = true if (!license?.htmlReadyLicenseContent.isNullOrBlank()) {
} else if (!license?.url.isNullOrBlank()) { openDialog = true
license.url?.also { } else if (!license?.url.isNullOrBlank()) {
try { license.url?.also {
uriHandler.openUri(it) try {
} catch (t: Throwable) { uriHandler.openUri(it)
throw Exception("Failed to open licence URL: $it", t) } catch (t: Throwable) {
} throw Exception("Failed to open licence URL: $it", t)
} }
} }
}, }
topRounded = index == 0, },
bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1 topRounded = index == 0,
) bottomRounded = index == (libraries?.libraries?.size ?: 1) - 1
)
BasicDialog( BasicDialog(
visible = openDialog, visible = openDialog,
title = { Text(library.name) }, title = { Text(library.name) },
text = { text = {
library.licenses.firstOrNull()?.licenseContent?.let { library.licenses.firstOrNull()?.licenseContent?.let {
Column(Modifier.verticalScroll(rememberScrollState())) { Column(Modifier.verticalScroll(rememberScrollState())) {
SelectionContainer { Text(text = it) } SelectionContainer { Text(text = it) }
}
} }
}, }
confirmButton = { },
FilledTonalButton( confirmButton = {
onClick = { FilledTonalButton(
openDialog = false onClick = {
VibrationUtils.performHapticFeedback(view) openDialog = false
}, VibrationUtils.performHapticFeedback(view)
shapes = ButtonDefaults.shapes() },
) { Text(stringResource(R.string.action_confirm)) } shapes = ButtonDefaults.shapes()
}, ) { Text(stringResource(R.string.action_confirm)) }
onDismissRequest = { openDialog = false } },
) onDismissRequest = { openDialog = false }
} )
} }
} }
} }

View file

@ -1,7 +1,6 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@ -12,16 +11,15 @@ import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.logic.model.DarkMode
import cn.super12138.todo.logic.model.PaletteStyle
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
import cn.super12138.todo.ui.pages.settings.components.appearance.contrast.ContrastPicker import cn.super12138.todo.ui.pages.settings.components.appearance.contrast.ContrastPicker
import cn.super12138.todo.ui.pages.settings.components.appearance.darkmode.DarkModePicker import cn.super12138.todo.ui.pages.settings.components.appearance.darkmode.DarkModePicker
import cn.super12138.todo.ui.pages.settings.components.appearance.palette.PalettePicker import cn.super12138.todo.ui.pages.settings.components.appearance.palette.PalettePicker
import cn.super12138.todo.ui.theme.ContrastLevel
import cn.super12138.todo.ui.theme.DarkMode
import cn.super12138.todo.ui.theme.PaletteStyle
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -40,44 +38,42 @@ fun SettingsAppearance(
title = stringResource(R.string.pref_appearance), title = stringResource(R.string.pref_appearance),
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item(key = 1) {
item(key = 1) { SwitchSettingsItem(
SwitchSettingsItem( checked = dynamicColor,
checked = dynamicColor, leadingIconRes = R.drawable.ic_wand_stars,
leadingIconRes = R.drawable.ic_wand_stars, title = stringResource(R.string.pref_appearance_dynamic_color),
title = stringResource(R.string.pref_appearance_dynamic_color), description = stringResource(R.string.pref_appearance_dynamic_color_desc),
description = stringResource(R.string.pref_appearance_dynamic_color_desc), onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } }, topRounded = true
topRounded = true )
) }
}
item(key = 2) { item(key = 2) {
DarkModePicker( DarkModePicker(
currentDarkMode = { DarkMode.fromId(darkMode) }, currentDarkMode = { DarkMode.fromId(darkMode) },
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } } onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
) )
} }
item(key = 3) { item(key = 3) {
PalettePicker( PalettePicker(
currentPalette = { PaletteStyle.fromId(paletteStyle) }, currentPalette = { PaletteStyle.fromId(paletteStyle) },
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } }, onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
isDynamicColor = dynamicColor, isDynamicColor = dynamicColor,
isDarkMode = DarkMode.fromId(darkMode), isDarkMode = DarkMode.fromId(darkMode),
contrastLevel = ContrastLevel.fromFloat(contrastLevel) contrastLevel = ContrastLevel.fromFloat(contrastLevel)
) )
} }
item(key = 4) { item(key = 4) {
ContrastPicker( ContrastPicker(
currentContrast = ContrastLevel.fromFloat(contrastLevel), currentContrast = ContrastLevel.fromFloat(contrastLevel),
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } }, onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } },
bottomRounded = true bottomRounded = true
) )
}
} }
} }
} }

View file

@ -5,7 +5,6 @@ import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
@ -25,7 +24,6 @@ import cn.super12138.todo.R
import cn.super12138.todo.ui.activities.MainActivity import cn.super12138.todo.ui.activities.MainActivity
import cn.super12138.todo.ui.components.ConfirmDialog import cn.super12138.todo.ui.components.ConfirmDialog
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@ -102,44 +100,45 @@ fun SettingsData(
onBack = onNavigateUp, onBack = onNavigateUp,
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item {
item { SettingsCategory(
SettingsCategory(title = stringResource(R.string.pref_category_data_management), first = true) title = stringResource(R.string.pref_category_data_management),
SettingsItem( first = true
leadingIconRes = R.drawable.ic_download, )
title = stringResource(R.string.pref_backup), SettingsItem(
description = stringResource(R.string.pref_backup_desc), leadingIconRes = R.drawable.ic_download,
onClick = { title = stringResource(R.string.pref_backup),
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip") description = stringResource(R.string.pref_backup_desc),
}, onClick = {
topRounded = true backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
) },
} topRounded = true
item { )
SettingsItem( }
leadingIconRes = R.drawable.ic_upload, item {
title = stringResource(R.string.pref_restore), SettingsItem(
description = stringResource(R.string.pref_restore_desc), leadingIconRes = R.drawable.ic_upload,
onClick = { title = stringResource(R.string.pref_restore),
restoreLauncher.launch(arrayOf("application/zip")) description = stringResource(R.string.pref_restore_desc),
}, onClick = {
bottomRounded = true restoreLauncher.launch(arrayOf("application/zip"))
) },
} bottomRounded = true
)
}
item { item {
SettingsCategory(stringResource(R.string.pref_category_category_management)) SettingsCategory(stringResource(R.string.pref_category_category_management))
SettingsItem( SettingsItem(
leadingIconRes = R.drawable.ic_category, leadingIconRes = R.drawable.ic_category,
title = stringResource(R.string.pref_category_category_management), title = stringResource(R.string.pref_category_category_management),
description = stringResource(R.string.pref_category_management_desc), description = stringResource(R.string.pref_category_management_desc),
onClick = toCategoryManager, onClick = toCategoryManager,
topRounded = true, topRounded = true,
bottomRounded = true bottomRounded = true
) )
}
} }
} }
} }

View file

@ -6,7 +6,6 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -30,8 +29,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.TodoFloatingActionButton import cn.super12138.todo.ui.components.TodoFloatingActionButton
import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -70,12 +69,10 @@ fun SettingsDataCategory(
) )
}, },
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier modifier = Modifier.fillMaxSize()
.fillMaxSize()
.padding(innerPadding) //TODO: 可能需要清除屏幕边距
) { ) {
if (categories.isEmpty()) { if (categories.isEmpty()) {
item { item {

View file

@ -1,14 +1,12 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@ -23,18 +21,16 @@ fun SettingsDeveloperOptions(
title = stringResource(R.string.pref_developer_options), title = stringResource(R.string.pref_developer_options),
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item {
item { SettingsItem(
SettingsItem( leadingIconRes = R.drawable.ic_padding,
leadingIconRes = R.drawable.ic_padding, title = stringResource(R.string.pref_padding),
title = stringResource(R.string.pref_padding), onClick = toPaddingPage,
onClick = toPaddingPage, topRounded = true,
topRounded = true, bottomRounded = true
bottomRounded = true )
)
}
} }
} }
} }

View file

@ -1,14 +1,12 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -19,7 +17,6 @@ import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.SortingMethod import cn.super12138.todo.logic.model.SortingMethod
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
@ -48,70 +45,69 @@ fun SettingsInterface(
title = stringResource(R.string.pref_interface_interaction), title = stringResource(R.string.pref_interface_interaction),
onBack = onNavigateUp, onBack = onNavigateUp,
modifier = modifier, modifier = modifier,
) { innerPadding -> ) {
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item {
item { SettingsCategory(
SettingsCategory(title = stringResource(R.string.pref_category_todo_list), first = true) title = stringResource(R.string.pref_category_todo_list),
SwitchSettingsItem( first = true
leadingIconRes = R.drawable.ic_checklist, )
title = stringResource(R.string.pref_show_completed), SwitchSettingsItem(
description = stringResource(R.string.pref_show_completed_desc), leadingIconRes = R.drawable.ic_checklist,
checked = showCompleted, title = stringResource(R.string.pref_show_completed),
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } }, description = stringResource(R.string.pref_show_completed_desc),
topRounded = true checked = showCompleted,
) onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
} topRounded = true
item { )
SettingsItem( }
leadingIconRes = R.drawable.ic_sort, item {
title = stringResource(R.string.pref_sorting_method), SettingsItem(
description = SortingMethod.fromId(sortingMethod).getDisplayName(context), leadingIconRes = R.drawable.ic_sort,
onClick = { showSortingMethodDialog = true }, title = stringResource(R.string.pref_sorting_method),
bottomRounded = true description = stringResource(SortingMethod.fromId(sortingMethod).nameRes),
) onClick = { showSortingMethodDialog = true },
} bottomRounded = true
)
}
item { item {
SettingsCategory(stringResource(R.string.pref_category_global)) SettingsCategory(stringResource(R.string.pref_category_global))
SwitchSettingsItem( SwitchSettingsItem(
checked = secureMode, checked = secureMode,
leadingIconRes = R.drawable.ic_shield, leadingIconRes = R.drawable.ic_shield,
title = stringResource(R.string.pref_secure_mode), title = stringResource(R.string.pref_secure_mode),
description = stringResource(R.string.pref_secure_mode_desc), description = stringResource(R.string.pref_secure_mode_desc),
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } }, onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } },
topRounded = true topRounded = true
) )
} }
item { item {
SwitchSettingsItem( SwitchSettingsItem(
checked = hapticFeedback, checked = hapticFeedback,
leadingIconRes = R.drawable.ic_touch_long, leadingIconRes = R.drawable.ic_touch_long,
title = stringResource(R.string.pref_haptic_feedback), title = stringResource(R.string.pref_haptic_feedback),
description = stringResource(R.string.pref_haptic_feedback_desc), description = stringResource(R.string.pref_haptic_feedback_desc),
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } }, onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } },
bottomRounded = true bottomRounded = true
) )
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info)) SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
}
} }
} }
val sortingList = remember { val sortingList = SortingMethod.entries.map {
SortingMethod.entries.map { SettingsRadioOptions(
SettingsRadioOptions( id = it.id,
id = it.id, text = stringResource(it.nameRes)
text = it.getDisplayName(context) )
)
}
} }
SettingsRadioDialog( SettingsRadioDialog(
visible = showSortingMethodDialog, visible = showSortingMethodDialog,
title = stringResource(R.string.pref_sorting_method), title = stringResource(R.string.pref_sorting_method),
currentOptions = SettingsRadioOptions( currentOptions = SettingsRadioOptions(
id = sortingMethod, id = sortingMethod,
text = SortingMethod.fromId(sortingMethod).getDisplayName(context) text = stringResource(SortingMethod.fromId(sortingMethod).nameRes)
), ),
options = sortingList, options = sortingList,
onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } }, onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } },

View file

@ -1,7 +1,6 @@
package cn.super12138.todo.ui.pages.settings package cn.super12138.todo.ui.pages.settings
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -9,7 +8,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.ui.components.TopAppBarScaffold import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.components.RoundedScreenContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
import cn.super12138.todo.ui.pages.settings.components.SettingsItem import cn.super12138.todo.ui.pages.settings.components.SettingsItem
@ -20,53 +18,49 @@ fun SettingsMain(
toInterfacePage: () -> Unit, toInterfacePage: () -> Unit,
toDataPage: () -> Unit, toDataPage: () -> Unit,
toAboutPage: () -> Unit, toAboutPage: () -> Unit,
onNavigateUp: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TopAppBarScaffold( TopAppBarScaffold(
title = stringResource(R.string.page_settings), title = stringResource(R.string.page_settings),
onBack = onNavigateUp,
modifier = modifier modifier = modifier
) { innerPadding -> ) {
RoundedScreenContainer(Modifier.padding(innerPadding)) { SettingsContainer(Modifier.fillMaxSize()) {
SettingsContainer(Modifier.fillMaxSize()) { item {
item { SettingsItem(
SettingsItem( leadingIconRes = R.drawable.ic_palette,
leadingIconRes = R.drawable.ic_palette, title = stringResource(R.string.pref_appearance),
title = stringResource(R.string.pref_appearance), description = stringResource(R.string.pref_appearance_desc),
description = stringResource(R.string.pref_appearance_desc), onClick = toAppearancePage,
onClick = toAppearancePage, topRounded = true
topRounded = true )
) }
}
item { item {
SettingsItem( SettingsItem(
leadingIconRes = R.drawable.ic_view_comfy, leadingIconRes = R.drawable.ic_view_comfy,
title = stringResource(R.string.pref_interface_interaction), title = stringResource(R.string.pref_interface_interaction),
description = stringResource(R.string.pref_interface_interaction_desc), description = stringResource(R.string.pref_interface_interaction_desc),
onClick = toInterfacePage onClick = toInterfacePage
) )
} }
item { item {
SettingsItem( SettingsItem(
leadingIconRes = R.drawable.ic_dns, leadingIconRes = R.drawable.ic_dns,
title = stringResource(R.string.pref_data), title = stringResource(R.string.pref_data),
description = stringResource(R.string.pref_data_desc), description = stringResource(R.string.pref_data_desc),
onClick = toDataPage onClick = toDataPage
) )
} }
item { item {
SettingsItem( SettingsItem(
leadingIconRes = R.drawable.ic_info, leadingIconRes = R.drawable.ic_info,
title = stringResource(R.string.pref_about), title = stringResource(R.string.pref_about),
description = stringResource(R.string.pref_about_desc), description = stringResource(R.string.pref_about_desc),
onClick = toAboutPage, onClick = toAboutPage,
bottomRounded = true bottomRounded = true
) )
}
} }
} }
} }

View file

@ -29,8 +29,8 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem
import cn.super12138.todo.ui.theme.ContrastLevel
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
@Composable @Composable
@ -50,8 +50,7 @@ fun ContrastPicker(
bottomRounded = bottomRounded, bottomRounded = bottomRounded,
modifier = modifier modifier = modifier
) { ) {
val contrastLevelName = val contrastLevelName = ContrastLevel.entries.map { stringResource(it.nameRes) }
ContrastLevel.entries.map { it.getDisplayName(context) }
var lastVibratedLevel by remember { mutableFloatStateOf(currentContrast.value) } var lastVibratedLevel by remember { mutableFloatStateOf(currentContrast.value) }
Slider( Slider(

View file

@ -9,13 +9,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.model.DarkMode
import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.DarkMode
@Composable @Composable
fun DarkModePicker( fun DarkModePicker(
@ -25,7 +24,6 @@ fun DarkModePicker(
topRounded: Boolean = false, topRounded: Boolean = false,
bottomRounded: Boolean = false bottomRounded: Boolean = false
) { ) {
val context = LocalContext.current
val isInDarkTheme = isSystemInDarkTheme() val isInDarkTheme = isSystemInDarkTheme()
val darkModeList = remember { DarkMode.entries.toList() } val darkModeList = remember { DarkMode.entries.toList() }
@ -50,7 +48,7 @@ fun DarkModePicker(
DarkModeItem( DarkModeItem(
iconRes = it.iconRes, iconRes = it.iconRes,
name = it.getDisplayName(context), name = stringResource(it.nameRes),
contentColor = contentColor, contentColor = contentColor,
containerColor = containerColor, containerColor = containerColor,
selected = isSelected, selected = isSelected,

View file

@ -22,14 +22,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import cn.super12138.todo.ui.theme.ContrastLevel import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.ui.theme.PaletteStyle import cn.super12138.todo.logic.model.PaletteStyle
import cn.super12138.todo.ui.theme.dynamicColorScheme import cn.super12138.todo.ui.theme.dynamicColorScheme
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
@ -44,7 +44,6 @@ fun PaletteItem(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val view = LocalView.current val view = LocalView.current
val context = LocalContext.current
Column( Column(
modifier = modifier modifier = modifier
@ -107,7 +106,7 @@ fun PaletteItem(
Spacer(Modifier.size(8.dp)) Spacer(Modifier.size(8.dp))
Text( Text(
text = paletteStyle.getDisplayName(context), text = stringResource(paletteStyle.nameRes),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = if (selected) { color = if (selected) {
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary

View file

@ -11,11 +11,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.logic.model.ContrastLevel
import cn.super12138.todo.logic.model.DarkMode
import cn.super12138.todo.logic.model.PaletteStyle
import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
import cn.super12138.todo.ui.theme.ContrastLevel
import cn.super12138.todo.ui.theme.DarkMode
import cn.super12138.todo.ui.theme.PaletteStyle
@Composable @Composable
fun PalettePicker( fun PalettePicker(

View file

@ -1,27 +0,0 @@
package cn.super12138.todo.ui.theme
import android.content.Context
import cn.super12138.todo.R
enum class ContrastLevel(val value: Float) {
VeryLow(-1f),
Low(-0.5f),
Default(0f),
Medium(0.5f),
High(1f);
fun getDisplayName(context: Context): String {
val resId = when (this) {
VeryLow -> R.string.contrast_very_low
Low -> R.string.contrast_low
Default -> R.string.contrast_default
Medium -> R.string.contrast_high
High -> R.string.contrast_very_high
}
return context.getString(resId)
}
companion object {
fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default
}
}

View file

@ -1,26 +0,0 @@
package cn.super12138.todo.ui.theme
import android.content.Context
import cn.super12138.todo.R
enum class DarkMode(
val id: Int,
val iconRes: Int
) {
FollowSystem(-1, R.drawable.ic_lightbulb_2),
Light(1, R.drawable.ic_light_mode),
Dark(2, R.drawable.ic_dark_mode);
fun getDisplayName(context: Context): String {
val resId = when (this) {
FollowSystem -> R.string.dark_mode_system
Light -> R.string.dark_mode_light
Dark -> R.string.dark_mode_dark
}
return context.getString(resId)
}
companion object {
fun fromId(id: Int) = entries.find { it.id == id } ?: FollowSystem
}
}

View file

@ -1,35 +0,0 @@
package cn.super12138.todo.ui.theme
import android.content.Context
import cn.super12138.todo.R
enum class PaletteStyle(val id: Int) {
TonalSpot(1),
Neutral(2),
Vibrant(3),
Expressive(4),
Rainbow(5),
FruitSalad(6),
Monochrome(7),
Fidelity(8),
Content(9);
fun getDisplayName(context: Context): String {
val resId = when (this) {
TonalSpot -> R.string.palette_tonal_spot
Neutral -> R.string.palette_neutral
Vibrant -> R.string.palette_vibrant
Expressive -> R.string.palette_expressive
Rainbow -> R.string.palette_rainbow
FruitSalad -> R.string.palette_fruit_salad
Monochrome -> R.string.palette_monochrome
Fidelity -> R.string.palette_fidelity
Content -> R.string.palette_content
}
return context.getString(resId)
}
companion object {
fun fromId(id: Int) = entries.firstOrNull { it.id == id } ?: TonalSpot
}
}

View file

@ -7,6 +7,7 @@ import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import cn.super12138.todo.logic.model.PaletteStyle
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable

View file

@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import cn.super12138.todo.logic.model.PaletteStyle
import com.kyant.m3color.hct.Hct import com.kyant.m3color.hct.Hct
import com.kyant.m3color.scheme.SchemeContent import com.kyant.m3color.scheme.SchemeContent
import com.kyant.m3color.scheme.SchemeExpressive import com.kyant.m3color.scheme.SchemeExpressive
@ -98,6 +99,5 @@ private inline fun Int.toColor(): Color = Color(this)
// https://github.com/jordond/MaterialKolor/blob/main/material-kolor/src/commonMain/kotlin/com/materialkolor/DynamicMaterialTheme.kt // https://github.com/jordond/MaterialKolor/blob/main/material-kolor/src/commonMain/kotlin/com/materialkolor/DynamicMaterialTheme.kt
@Composable @Composable
private fun Color.animate(animationSpec: AnimationSpec<Color> = spring()): Color { private fun Color.animate(animationSpec: AnimationSpec<Color> = spring()): Color =
return animateColorAsState(this, animationSpec).value animateColorAsState(this, animationSpec).value
}

View file

@ -5,12 +5,15 @@ import android.net.Uri
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation3.runtime.NavKey
import cn.super12138.todo.TodoApp import cn.super12138.todo.TodoApp
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.Repository import cn.super12138.todo.logic.Repository
import cn.super12138.todo.logic.database.TodoEntity import cn.super12138.todo.logic.database.TodoEntity
import cn.super12138.todo.logic.datastore.DataStoreManager import cn.super12138.todo.logic.datastore.DataStoreManager
import cn.super12138.todo.logic.model.SortingMethod import cn.super12138.todo.logic.model.SortingMethod
import cn.super12138.todo.ui.navigation.TodoScreen
import cn.super12138.todo.ui.navigation.TopLevelBackStack
import cn.super12138.todo.utils.FileUtils import cn.super12138.todo.utils.FileUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -32,6 +35,8 @@ import java.util.zip.ZipOutputStream
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
val topLevelBackStack = TopLevelBackStack<NavKey>(startKey = TodoScreen.Overview)
// 待办 // 待办
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos() private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
val sortedTodos: Flow<List<TodoEntity>> = val sortedTodos: Flow<List<TodoEntity>> =

View file

@ -2,6 +2,8 @@ package cn.super12138.todo.utils
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -39,4 +41,13 @@ object SystemUtils {
return sdf.format(currentTime) return sdf.format(currentTime)
} }
}
fun ComponentActivity.configureEdgeToEdge() {
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Force the 3-button navigation bar to be transparent
// See: https://developer.android.com/develop/ui/views/layout/edge-to-edge#create-transparent
window.isNavigationBarContrastEnforced = false
}
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M640,400Q657,400 668.5,388.5Q680,377 680,360Q680,343 668.5,331.5Q657,320 640,320L520,320Q503,320 491.5,331.5Q480,343 480,360Q480,377 491.5,388.5Q503,400 520,400L640,400ZM640,640Q657,640 668.5,628.5Q680,617 680,600Q680,583 668.5,571.5Q657,560 640,560L520,560Q503,560 491.5,571.5Q480,583 480,600Q480,617 491.5,628.5Q503,640 520,640L640,640ZM360,440Q393,440 416.5,416.5Q440,393 440,360Q440,327 416.5,303.5Q393,280 360,280Q327,280 303.5,303.5Q280,327 280,360Q280,393 303.5,416.5Q327,440 360,440ZM360,680Q393,680 416.5,656.5Q440,633 440,600Q440,567 416.5,543.5Q393,520 360,520Q327,520 303.5,543.5Q280,567 280,600Q280,633 303.5,656.5Q327,680 360,680ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M640,400Q657,400 668.5,388.5Q680,377 680,360Q680,343 668.5,331.5Q657,320 640,320L520,320Q503,320 491.5,331.5Q480,343 480,360Q480,377 491.5,388.5Q503,400 520,400L640,400ZM640,640Q657,640 668.5,628.5Q680,617 680,600Q680,583 668.5,571.5Q657,560 640,560L520,560Q503,560 491.5,571.5Q480,583 480,600Q480,617 491.5,628.5Q503,640 520,640L640,640ZM360,440Q393,440 416.5,416.5Q440,393 440,360Q440,327 416.5,303.5Q393,280 360,280Q327,280 303.5,303.5Q280,327 280,360Q280,393 303.5,416.5Q327,440 360,440ZM360,680Q393,680 416.5,656.5Q440,633 440,600Q440,567 416.5,543.5Q393,520 360,520Q327,520 303.5,543.5Q280,567 280,600Q280,633 303.5,656.5Q327,680 360,680ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M433,880Q406,880 386.5,862Q367,844 363,818L354,752Q341,747 329.5,740Q318,733 307,725L245,751Q220,762 195,753Q170,744 156,721L109,639Q95,616 101,590Q107,564 128,547L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L128,413Q107,396 101,370Q95,344 109,321L156,239Q170,216 195,207Q220,198 245,209L307,235Q318,227 330,220Q342,213 354,208L363,142Q367,116 386.5,98Q406,80 433,80L527,80Q554,80 573.5,98Q593,116 597,142L606,208Q619,213 630.5,220Q642,227 653,235L715,209Q740,198 765,207Q790,216 804,239L851,321Q865,344 859,370Q853,396 832,413L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L831,547Q852,564 858,590Q864,616 850,639L802,721Q788,744 763,753Q738,762 713,751L653,725Q642,733 630,740Q618,747 606,752L597,818Q593,844 573.5,862Q554,880 527,880L433,880ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M280,680L360,680L360,280L280,280L280,680ZM600,600L680,600L680,280L600,280L600,600ZM440,480L520,480L520,280L440,280L440,480ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM280,680L360,680L360,280L280,280L280,680ZM600,600L680,600L680,280L600,280L600,600ZM440,480L520,480L520,280L440,280L440,480Z" />
</vector>

View file

@ -115,4 +115,7 @@
<string name="pref_developer_options">开发者选项</string> <string name="pref_developer_options">开发者选项</string>
<string name="pref_developer_options_desc">一些调试选项,仅供开发使用</string> <string name="pref_developer_options_desc">一些调试选项,仅供开发使用</string>
<string name="pref_padding">边距</string> <string name="pref_padding">边距</string>
<string name="tip_selected">已选择</string>
<string name="page_tasks">待办</string>
<string name="page_overview">概览</string>
</resources> </resources>

View file

@ -117,4 +117,6 @@
<string name="pref_developer_options_desc">Some debugging options, only for DEVELOPMENT use</string> <string name="pref_developer_options_desc">Some debugging options, only for DEVELOPMENT use</string>
<string name="pref_padding">Padding</string> <string name="pref_padding">Padding</string>
<string name="tip_selected">Selected</string> <string name="tip_selected">Selected</string>
<string name="page_tasks">Tasks</string>
<string name="page_overview">Overview</string>
</resources> </resources>