feat: 使用规范化布局(导航方式1)
This commit is contained in:
parent
562293fd17
commit
a6e12f4981
47 changed files with 867 additions and 767 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,17 @@
|
|||
package cn.super12138.todo.logic.model
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import cn.super12138.todo.R
|
||||
|
||||
enum class Priority(val value: Float) {
|
||||
Urgent(2f),
|
||||
Important(1f),
|
||||
Default(0f),
|
||||
NotImportant(-1f),
|
||||
NotUrgent(-2f);
|
||||
|
||||
fun displayName(context: Context): String {
|
||||
val resId = when (this) {
|
||||
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)
|
||||
}
|
||||
enum class Priority(
|
||||
val value: Float,
|
||||
@StringRes val nameRes: Int
|
||||
) {
|
||||
Urgent(value = 2f, nameRes = R.string.priority_urgent),
|
||||
Important(value = 1f, nameRes = R.string.priority_important),
|
||||
Default(value = 0f, nameRes = R.string.priority_default),
|
||||
NotImportant(value = -1f, nameRes = R.string.priority_not_important),
|
||||
NotUrgent(value = -2f, nameRes = R.string.priority_not_urgent);
|
||||
|
||||
companion object {
|
||||
fun fromFloat(float: Float) = entries.find { it.value == float } ?: Default
|
||||
|
|
|
|||
|
|
@ -1,38 +1,29 @@
|
|||
package cn.super12138.todo.logic.model
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
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);
|
||||
|
||||
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)
|
||||
}
|
||||
AlphabeticalDescending(id = 6, nameRes = R.string.sorting_alphabetical_descending);
|
||||
|
||||
companion object {
|
||||
fun fromId(id: Int) = entries.find { it.id == id } ?: Sequential
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.super12138.todo.ui
|
||||
|
||||
import androidx.compose.foundation.shape.CornerBasedShape
|
||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -58,10 +57,10 @@ object TodoDefaults {
|
|||
|
||||
val ScreenContainerShape: Shape
|
||||
@Composable
|
||||
get() = MaterialTheme.shapes.large.copy(
|
||||
get() = MaterialTheme.shapes.large/*.copy(
|
||||
bottomStart = ZeroCornerSize,
|
||||
bottomEnd = ZeroCornerSize
|
||||
)
|
||||
)*/
|
||||
|
||||
val ContainerColor: Color
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package cn.super12138.todo.ui.activities
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -16,11 +14,12 @@ import androidx.core.view.WindowCompat
|
|||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
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.theme.DarkMode
|
||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||
import cn.super12138.todo.ui.theme.ToDoTheme
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
import cn.super12138.todo.utils.configureEdgeToEdge
|
||||
|
||||
class CrashActivity : ComponentActivity() {
|
||||
companion object {
|
||||
|
|
@ -32,10 +31,7 @@ class CrashActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
configureEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val crashLogs = intent.getStringExtra("crash_logs")
|
||||
|
|
|
|||
|
|
@ -1,42 +1,45 @@
|
|||
package cn.super12138.todo.ui.activities
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
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.view.WindowCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cn.super12138.todo.constants.Constants
|
||||
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.components.Konfetti
|
||||
import cn.super12138.todo.ui.navigation.TodoDestinations
|
||||
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.viewmodels.MainViewModel
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
import cn.super12138.todo.utils.configureEdgeToEdge
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
enableEdgeToEdge()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
configureEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val mainViewModel: MainViewModel = viewModel()
|
||||
val navigationBackStack = mainViewModel.topLevelBackStack
|
||||
val showConfetti = mainViewModel.showConfetti
|
||||
// 主题
|
||||
val dynamicColor by DataStoreManager.dynamicColorFlow.collectAsState(initial = Constants.PREF_DYNAMIC_COLOR_DEFAULT)
|
||||
|
|
@ -84,10 +87,33 @@ class MainActivity : ComponentActivity() {
|
|||
dynamicColor = dynamicColor
|
||||
) {
|
||||
Surface(color = TodoDefaults.BackgroundColor) {
|
||||
TodoNavigation(
|
||||
viewModel = mainViewModel,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
NavigationSuiteScaffold(
|
||||
navigationSuiteItems = {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
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.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -20,6 +18,8 @@ import androidx.compose.material3.TopAppBar
|
|||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -33,10 +33,9 @@ import cn.super12138.todo.utils.VibrationUtils
|
|||
* * 内容默认由 Box 容器包裹;实际使用时推荐配合 Column 或 Row
|
||||
*
|
||||
* @param title 标题文本
|
||||
* @param onBack 当返回按钮被按下时的操作
|
||||
* @param contentWindowInsets 内容边距,通常用于将内容和系统状态栏等隔开;可以使用 `WindowInsets.safeContent`
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TopAppBarScaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -46,7 +45,7 @@ fun TopAppBarScaffold(
|
|||
floatingActionButton: @Composable () -> Unit = {},
|
||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val view = LocalView.current
|
||||
TopAppBarScaffold(
|
||||
|
|
@ -77,10 +76,37 @@ fun TopAppBarScaffold(
|
|||
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
//TODO: 应用圆角效果
|
||||
Box { content(innerPadding) }
|
||||
}
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
|
@ -94,9 +120,9 @@ fun TopAppBarScaffold(
|
|||
floatingActionButton: @Composable () -> Unit = {},
|
||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
TopAppBarScaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
|
@ -112,8 +138,37 @@ fun TopAppBarScaffold(
|
|||
floatingActionButton = floatingActionButton,
|
||||
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
containerColor = TodoDefaults.BackgroundColor,
|
||||
) { innerPadding ->
|
||||
Box { content(innerPadding) }
|
||||
}
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
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.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.ui.Modifier
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import cn.super12138.todo.ui.pages.editor.TodoEditorPage
|
||||
import cn.super12138.todo.ui.pages.main.MainPage
|
||||
import cn.super12138.todo.ui.pages.overview.OverviewPage
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAppearance
|
||||
|
|
@ -28,17 +35,39 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f
|
|||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun TodoNavigation(
|
||||
backStack: TopLevelBackStack<NavKey>,
|
||||
modifier: Modifier = Modifier,
|
||||
backStack: NavBackStack<NavKey> = rememberNavBackStack(TodoScreen.Main),
|
||||
viewModel: MainViewModel
|
||||
) {
|
||||
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 {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
backStack = backStack.backStack,
|
||||
onBack = ::onBack,
|
||||
transitionSpec = {
|
||||
materialSharedAxisX(
|
||||
|
|
@ -50,16 +79,27 @@ fun TodoNavigation(
|
|||
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
|
||||
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() })
|
||||
},
|
||||
|
||||
entryProvider = entryProvider {
|
||||
entry<TodoScreen.Main> {
|
||||
entry<TodoScreen.Overview>(metadata = anim) {
|
||||
OverviewPage()
|
||||
}
|
||||
entry<TodoScreen.Tasks>(metadata = anim) {
|
||||
MainPage(
|
||||
viewModel = viewModel,
|
||||
toTodoEditPage = { backStack.add(TodoScreen.Editor(it)) },
|
||||
toSettingsPage = { backStack.add(TodoScreen.SettingsMain) },
|
||||
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 ->
|
||||
TodoEditorPage(
|
||||
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> {
|
||||
SettingsAppearance(onNavigateUp = ::onBack)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
sealed class TodoScreen : NavKey {
|
||||
@Serializable
|
||||
data object Main : TodoScreen()
|
||||
data object Overview : TodoScreen()
|
||||
|
||||
@Serializable
|
||||
data object Tasks : TodoScreen()
|
||||
|
||||
@Serializable
|
||||
data class Editor(val toDo: TodoEntity?) : TodoScreen()
|
||||
|
||||
@Serializable
|
||||
data object SettingsMain : TodoScreen()
|
||||
data object Settings : TodoScreen()
|
||||
|
||||
@Serializable
|
||||
data object SettingsAppearance : TodoScreen()
|
||||
|
|
@ -41,4 +44,4 @@ sealed class TodoScreen : NavKey {
|
|||
|
||||
@Serializable
|
||||
data object SettingsDevPadding : TodoScreen()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -41,11 +41,10 @@ import cn.super12138.todo.R
|
|||
import cn.super12138.todo.constants.Constants
|
||||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.components.ChipItem
|
||||
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.TopAppBarScaffold
|
||||
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.TodoContentTextField
|
||||
|
|
@ -159,13 +158,10 @@ fun TodoEditorPage(
|
|||
},
|
||||
onBack = ::checkModifiedBeforeBack,
|
||||
modifier = modifier
|
||||
) { innerPadding ->
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
|
||||
.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
TodoContentTextField(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.LiveRegionMode
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.liveRegion
|
||||
|
|
@ -35,7 +36,7 @@ fun TodoPrioritySlider(
|
|||
val view = LocalView.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() }
|
||||
|
||||
Slider(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,18 @@ package cn.super12138.todo.ui.pages.main
|
|||
|
||||
import androidx.activity.compose.BackHandler
|
||||
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.fillMaxSize
|
||||
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.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.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -21,13 +26,19 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
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 androidx.navigation3.ui.LocalNavAnimatedContentScope
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
import cn.super12138.todo.logic.model.Priority
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.components.ConfirmDialog
|
||||
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.viewmodels.MainViewModel
|
||||
|
||||
|
|
@ -37,9 +48,7 @@ fun MainPage(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: MainViewModel,
|
||||
toTodoEditPage: (TodoEntity?) -> Unit,
|
||||
toSettingsPage: () -> Unit,
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
// windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
|
||||
) {
|
||||
val animatedVisibilityScope = LocalNavAnimatedContentScope.current
|
||||
|
||||
|
|
@ -53,8 +62,8 @@ fun MainPage(
|
|||
val selectedTodoIds by remember { derivedStateOf { selectedTodos.value } }
|
||||
val inSelectedMode by remember { derivedStateOf { !selectedTodoIds.isEmpty() } }
|
||||
val toDoList by remember { derivedStateOf { toDos.value } }
|
||||
val totalTasks by remember { derivedStateOf { toDoList.size } }
|
||||
val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } }
|
||||
/*val totalTasks by remember { derivedStateOf { toDoList.size } }
|
||||
val completedTasks by remember { derivedStateOf { toDoList.count { it.isCompleted } } }*/
|
||||
val filteredTodoList =
|
||||
if (showCompleted) toDoList else toDoList.filter { item -> !item.isCompleted }
|
||||
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||
|
|
@ -62,15 +71,14 @@ fun MainPage(
|
|||
// 当按下返回键(或进行返回操作)时清空选择,仅在非选择模式下生效
|
||||
BackHandler(inSelectedMode) { viewModel.clearAllTodoSelection() }
|
||||
|
||||
Scaffold(
|
||||
TopAppBarScaffold(
|
||||
topBar = {
|
||||
TodoTopAppBar(
|
||||
selectedTodoIds = selectedTodoIds,
|
||||
selectedMode = inSelectedMode,
|
||||
onCancelSelect = { viewModel.clearAllTodoSelection() },
|
||||
onSelectAll = { viewModel.selectAllTodos() },
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true },
|
||||
toSettingsPage = toSettingsPage
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true }
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
@ -94,84 +102,68 @@ fun MainPage(
|
|||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
||||
modifier = modifier
|
||||
) { innerPadding ->
|
||||
/*val isMediumScreen =
|
||||
windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND)
|
||||
if (isMediumScreen) {
|
||||
Row(
|
||||
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()
|
||||
)
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
ProgressFragment(
|
||||
totalTasks = totalTasks,
|
||||
completedTasks = completedTasks,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
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,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
item {
|
||||
Spacer(modifier = Modifier.size(TodoDefaults.settingsItemVerticalPadding))
|
||||
}
|
||||
|
||||
if (filteredTodoList.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.tip_no_task),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(
|
||||
items = filteredTodoList,
|
||||
key = { it.id }
|
||||
) {
|
||||
TodoCard(
|
||||
// id = item.id,
|
||||
content = it.content,
|
||||
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 }
|
||||
)
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ fun TodoCard(
|
|||
modifier = Modifier.padding(start = 5.dp)
|
||||
) {
|
||||
Text(
|
||||
text = priority.displayName(context),
|
||||
text = stringResource(priority.nameRes),
|
||||
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
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.tooling.preview.Preview
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -40,15 +42,14 @@ fun TodoTopAppBar(
|
|||
onCancelSelect: () -> Unit,
|
||||
onSelectAll: () -> Unit,
|
||||
onDeleteSelectedTodo: () -> Unit,
|
||||
toSettingsPage: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val animatedTopAppBarColors by animateColorAsState(
|
||||
targetValue = if (selectedMode) {
|
||||
TopAppBarDefaults.topAppBarColors().scrolledContainerColor
|
||||
MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
} else {
|
||||
TopAppBarDefaults.topAppBarColors().containerColor
|
||||
TodoDefaults.BackgroundColor
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -103,52 +104,29 @@ fun TodoTopAppBar(
|
|||
}
|
||||
},
|
||||
actions = {
|
||||
AnimatedContent(
|
||||
targetState = selectedMode,
|
||||
transitionSpec = {
|
||||
(
|
||||
fadeIn() + scaleIn(initialScale = 0.92f)
|
||||
).togetherWith(
|
||||
fadeOut(animationSpec = tween(90))
|
||||
)
|
||||
}
|
||||
) { inSelectedMode ->
|
||||
if (!inSelectedMode) {
|
||||
AnimatedVisibility(visible = selectedMode) {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
toSettingsPage()
|
||||
onSelectAll()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_settings),
|
||||
contentDescription = stringResource(R.string.page_settings)
|
||||
painter = painterResource(R.drawable.ic_select_all),
|
||||
contentDescription = stringResource(R.string.tip_select_all)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
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)
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
onDeleteSelectedTodo()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete),
|
||||
contentDescription = stringResource(R.string.action_delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +148,6 @@ private fun TodoTopAppBarPreview() {
|
|||
selectedMode = selectedMode,
|
||||
onCancelSelect = { selectedMode = !selectedMode },
|
||||
onSelectAll = { },
|
||||
onDeleteSelectedTodo = { },
|
||||
toSettingsPage = { selectedMode = !selectedMode }
|
||||
onDeleteSelectedTodo = { }
|
||||
)
|
||||
}
|
||||
|
|
@ -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("总任务")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package cn.super12138.todo.ui.pages.settings
|
|||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -19,7 +18,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
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.pages.settings.components.SettingsContainer
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||
|
|
@ -39,7 +37,7 @@ fun SettingsAbout(
|
|||
title = stringResource(R.string.pref_about),
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
var clickCount by remember { mutableIntStateOf(0) }
|
||||
|
|
@ -57,60 +55,58 @@ fun SettingsAbout(
|
|||
}
|
||||
}
|
||||
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_numbers,
|
||||
title = stringResource(R.string.pref_app_version),
|
||||
description = SystemUtils.getAppVersion(context),
|
||||
onClick = {
|
||||
clickCount++
|
||||
if (clickCount == 5) {
|
||||
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
|
||||
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
clickCount = 0
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_numbers,
|
||||
title = stringResource(R.string.pref_app_version),
|
||||
description = SystemUtils.getAppVersion(context),
|
||||
onClick = {
|
||||
clickCount++
|
||||
if (clickCount == 5) {
|
||||
if ((System.currentTimeMillis() % 2) == 0.toLong()) {
|
||||
Toast.makeText(context, "🍨", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "✈️", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_person_4,
|
||||
title = stringResource(R.string.pref_developer),
|
||||
description = stringResource(R.string.developer_name),
|
||||
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIcon = GitHubIcon,
|
||||
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,
|
||||
title = stringResource(R.string.pref_licence),
|
||||
description = stringResource(R.string.pref_licence_desc),
|
||||
onClick = toLicencePage
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_code_blocks,
|
||||
title = stringResource(R.string.pref_developer_options),
|
||||
description = stringResource(R.string.pref_developer_options_desc),
|
||||
onClick = toDevPage,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
clickCount = 0
|
||||
}
|
||||
},
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_person_4,
|
||||
title = stringResource(R.string.pref_developer),
|
||||
description = stringResource(R.string.developer_name),
|
||||
onClick = { uriHandler.openUri(Constants.DEVELOPER_GITHUB) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIcon = GitHubIcon,
|
||||
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,
|
||||
title = stringResource(R.string.pref_licence),
|
||||
description = stringResource(R.string.pref_licence_desc),
|
||||
onClick = toLicencePage
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_code_blocks,
|
||||
title = stringResource(R.string.pref_developer_options),
|
||||
description = stringResource(R.string.pref_developer_options_desc),
|
||||
onClick = toDevPage,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
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 cn.super12138.todo.R
|
||||
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.pages.settings.components.SettingsContainer
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||
|
|
@ -53,103 +51,100 @@ fun SettingsAboutLicence(
|
|||
title = stringResource(R.string.pref_licence),
|
||||
onBack = onNavigateUp,
|
||||
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)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
itemsIndexed(
|
||||
items = libraries?.libraries ?: listOf(),
|
||||
key = { _, library -> library.artifactId }
|
||||
) { index, library ->
|
||||
var openDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
SettingsItem(
|
||||
headlineContent = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
SettingsItem(
|
||||
headlineContent = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = library.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
val version = library.artifactVersion
|
||||
if (version != null) {
|
||||
Text(
|
||||
text = library.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
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
|
||||
text = version,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
if (library.licenses.isNotEmpty()) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
val author = library.author
|
||||
if (author.isNotBlank()) {
|
||||
Text(
|
||||
text = author,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
if (library.licenses.isNotEmpty()) {
|
||||
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()
|
||||
if (!license?.htmlReadyLicenseContent.isNullOrBlank()) {
|
||||
openDialog = true
|
||||
} else if (!license?.url.isNullOrBlank()) {
|
||||
license.url?.also {
|
||||
try {
|
||||
uriHandler.openUri(it)
|
||||
} catch (t: Throwable) {
|
||||
throw Exception("Failed to open licence URL: $it", t)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
val license = library.licenses.firstOrNull()
|
||||
if (!license?.htmlReadyLicenseContent.isNullOrBlank()) {
|
||||
openDialog = true
|
||||
} else if (!license?.url.isNullOrBlank()) {
|
||||
license.url?.also {
|
||||
try {
|
||||
uriHandler.openUri(it)
|
||||
} 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(
|
||||
visible = openDialog,
|
||||
title = { Text(library.name) },
|
||||
text = {
|
||||
library.licenses.firstOrNull()?.licenseContent?.let {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
SelectionContainer { Text(text = it) }
|
||||
}
|
||||
BasicDialog(
|
||||
visible = openDialog,
|
||||
title = { Text(library.name) },
|
||||
text = {
|
||||
library.licenses.firstOrNull()?.licenseContent?.let {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
SelectionContainer { Text(text = it) }
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
openDialog = false
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
},
|
||||
shapes = ButtonDefaults.shapes()
|
||||
) { Text(stringResource(R.string.action_confirm)) }
|
||||
},
|
||||
onDismissRequest = { openDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
openDialog = false
|
||||
VibrationUtils.performHapticFeedback(view)
|
||||
},
|
||||
shapes = ButtonDefaults.shapes()
|
||||
) { Text(stringResource(R.string.action_confirm)) }
|
||||
},
|
||||
onDismissRequest = { openDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -12,16 +11,15 @@ import androidx.compose.ui.res.stringResource
|
|||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
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.RoundedScreenContainer
|
||||
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.appearance.contrast.ContrastPicker
|
||||
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.theme.ContrastLevel
|
||||
import cn.super12138.todo.ui.theme.DarkMode
|
||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -40,44 +38,42 @@ fun SettingsAppearance(
|
|||
title = stringResource(R.string.pref_appearance),
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item(key = 1) {
|
||||
SwitchSettingsItem(
|
||||
checked = dynamicColor,
|
||||
leadingIconRes = R.drawable.ic_wand_stars,
|
||||
title = stringResource(R.string.pref_appearance_dynamic_color),
|
||||
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item(key = 1) {
|
||||
SwitchSettingsItem(
|
||||
checked = dynamicColor,
|
||||
leadingIconRes = R.drawable.ic_wand_stars,
|
||||
title = stringResource(R.string.pref_appearance_dynamic_color),
|
||||
description = stringResource(R.string.pref_appearance_dynamic_color_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setDynamicColor(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
|
||||
item(key = 2) {
|
||||
DarkModePicker(
|
||||
currentDarkMode = { DarkMode.fromId(darkMode) },
|
||||
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
|
||||
)
|
||||
}
|
||||
item(key = 2) {
|
||||
DarkModePicker(
|
||||
currentDarkMode = { DarkMode.fromId(darkMode) },
|
||||
onDarkModeChange = { scope.launch { DataStoreManager.setDarkMode(it.id) } }
|
||||
)
|
||||
}
|
||||
|
||||
item(key = 3) {
|
||||
PalettePicker(
|
||||
currentPalette = { PaletteStyle.fromId(paletteStyle) },
|
||||
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
|
||||
isDynamicColor = dynamicColor,
|
||||
isDarkMode = DarkMode.fromId(darkMode),
|
||||
contrastLevel = ContrastLevel.fromFloat(contrastLevel)
|
||||
)
|
||||
}
|
||||
item(key = 3) {
|
||||
PalettePicker(
|
||||
currentPalette = { PaletteStyle.fromId(paletteStyle) },
|
||||
onPaletteChange = { scope.launch { DataStoreManager.setPaletteStyle(it.id) } },
|
||||
isDynamicColor = dynamicColor,
|
||||
isDarkMode = DarkMode.fromId(darkMode),
|
||||
contrastLevel = ContrastLevel.fromFloat(contrastLevel)
|
||||
)
|
||||
}
|
||||
|
||||
item(key = 4) {
|
||||
ContrastPicker(
|
||||
currentContrast = ContrastLevel.fromFloat(contrastLevel),
|
||||
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } },
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
item(key = 4) {
|
||||
ContrastPicker(
|
||||
currentContrast = ContrastLevel.fromFloat(contrastLevel),
|
||||
onContrastChange = { scope.launch { DataStoreManager.setContrastLevel(it.value) } },
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.content.Intent
|
|||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
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.components.ConfirmDialog
|
||||
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.SettingsContainer
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||
|
|
@ -102,44 +100,45 @@ fun SettingsData(
|
|||
onBack = onNavigateUp,
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsCategory(title = stringResource(R.string.pref_category_data_management), first = true)
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_download,
|
||||
title = stringResource(R.string.pref_backup),
|
||||
description = stringResource(R.string.pref_backup_desc),
|
||||
onClick = {
|
||||
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
|
||||
},
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_upload,
|
||||
title = stringResource(R.string.pref_restore),
|
||||
description = stringResource(R.string.pref_restore_desc),
|
||||
onClick = {
|
||||
restoreLauncher.launch(arrayOf("application/zip"))
|
||||
},
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsCategory(
|
||||
title = stringResource(R.string.pref_category_data_management),
|
||||
first = true
|
||||
)
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_download,
|
||||
title = stringResource(R.string.pref_backup),
|
||||
description = stringResource(R.string.pref_backup_desc),
|
||||
onClick = {
|
||||
backupLauncher.launch("Todo-backup-${SystemUtils.getTime()}.zip")
|
||||
},
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_upload,
|
||||
title = stringResource(R.string.pref_restore),
|
||||
description = stringResource(R.string.pref_restore_desc),
|
||||
onClick = {
|
||||
restoreLauncher.launch(arrayOf("application/zip"))
|
||||
},
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsCategory(stringResource(R.string.pref_category_category_management))
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_category,
|
||||
title = stringResource(R.string.pref_category_category_management),
|
||||
description = stringResource(R.string.pref_category_management_desc),
|
||||
onClick = toCategoryManager,
|
||||
topRounded = true,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsCategory(stringResource(R.string.pref_category_category_management))
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_category,
|
||||
title = stringResource(R.string.pref_category_category_management),
|
||||
description = stringResource(R.string.pref_category_management_desc),
|
||||
onClick = toCategoryManager,
|
||||
topRounded = true,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.compose.animation.core.spring
|
|||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
|
|
@ -30,8 +29,8 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.unit.IntOffset
|
||||
import cn.super12138.todo.R
|
||||
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.TopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.settings.components.category.CategoryItem
|
||||
import cn.super12138.todo.ui.pages.settings.components.category.CategoryPromptDialog
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -70,12 +69,10 @@ fun SettingsDataCategory(
|
|||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding) //TODO: 可能需要清除屏幕边距
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
if (categories.isEmpty()) {
|
||||
item {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
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.SettingsItem
|
||||
|
||||
|
|
@ -23,18 +21,16 @@ fun SettingsDeveloperOptions(
|
|||
title = stringResource(R.string.pref_developer_options),
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_padding,
|
||||
title = stringResource(R.string.pref_padding),
|
||||
onClick = toPaddingPage,
|
||||
topRounded = true,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_padding,
|
||||
title = stringResource(R.string.pref_padding),
|
||||
onClick = toPaddingPage,
|
||||
topRounded = true,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -19,7 +17,6 @@ import cn.super12138.todo.R
|
|||
import cn.super12138.todo.constants.Constants
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
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.pages.settings.components.SettingsCategory
|
||||
import cn.super12138.todo.ui.pages.settings.components.SettingsContainer
|
||||
|
|
@ -48,70 +45,69 @@ fun SettingsInterface(
|
|||
title = stringResource(R.string.pref_interface_interaction),
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsCategory(title = stringResource(R.string.pref_category_todo_list), first = true)
|
||||
SwitchSettingsItem(
|
||||
leadingIconRes = R.drawable.ic_checklist,
|
||||
title = stringResource(R.string.pref_show_completed),
|
||||
description = stringResource(R.string.pref_show_completed_desc),
|
||||
checked = showCompleted,
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_sort,
|
||||
title = stringResource(R.string.pref_sorting_method),
|
||||
description = SortingMethod.fromId(sortingMethod).getDisplayName(context),
|
||||
onClick = { showSortingMethodDialog = true },
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsCategory(
|
||||
title = stringResource(R.string.pref_category_todo_list),
|
||||
first = true
|
||||
)
|
||||
SwitchSettingsItem(
|
||||
leadingIconRes = R.drawable.ic_checklist,
|
||||
title = stringResource(R.string.pref_show_completed),
|
||||
description = stringResource(R.string.pref_show_completed_desc),
|
||||
checked = showCompleted,
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setShowCompleted(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_sort,
|
||||
title = stringResource(R.string.pref_sorting_method),
|
||||
description = stringResource(SortingMethod.fromId(sortingMethod).nameRes),
|
||||
onClick = { showSortingMethodDialog = true },
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsCategory(stringResource(R.string.pref_category_global))
|
||||
SwitchSettingsItem(
|
||||
checked = secureMode,
|
||||
leadingIconRes = R.drawable.ic_shield,
|
||||
title = stringResource(R.string.pref_secure_mode),
|
||||
description = stringResource(R.string.pref_secure_mode_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsCategory(stringResource(R.string.pref_category_global))
|
||||
SwitchSettingsItem(
|
||||
checked = secureMode,
|
||||
leadingIconRes = R.drawable.ic_shield,
|
||||
title = stringResource(R.string.pref_secure_mode),
|
||||
description = stringResource(R.string.pref_secure_mode_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setSecureMode(it) } },
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchSettingsItem(
|
||||
checked = hapticFeedback,
|
||||
leadingIconRes = R.drawable.ic_touch_long,
|
||||
title = stringResource(R.string.pref_haptic_feedback),
|
||||
description = stringResource(R.string.pref_haptic_feedback_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } },
|
||||
bottomRounded = true
|
||||
)
|
||||
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
|
||||
}
|
||||
item {
|
||||
SwitchSettingsItem(
|
||||
checked = hapticFeedback,
|
||||
leadingIconRes = R.drawable.ic_touch_long,
|
||||
title = stringResource(R.string.pref_haptic_feedback),
|
||||
description = stringResource(R.string.pref_haptic_feedback_desc),
|
||||
onCheckedChange = { scope.launch { DataStoreManager.setHapticFeedback(it) } },
|
||||
bottomRounded = true
|
||||
)
|
||||
SettingsPlainBox(stringResource(R.string.pref_haptic_feedback_more_info))
|
||||
}
|
||||
}
|
||||
|
||||
val sortingList = remember {
|
||||
SortingMethod.entries.map {
|
||||
SettingsRadioOptions(
|
||||
id = it.id,
|
||||
text = it.getDisplayName(context)
|
||||
)
|
||||
}
|
||||
val sortingList = SortingMethod.entries.map {
|
||||
SettingsRadioOptions(
|
||||
id = it.id,
|
||||
text = stringResource(it.nameRes)
|
||||
)
|
||||
}
|
||||
SettingsRadioDialog(
|
||||
visible = showSortingMethodDialog,
|
||||
title = stringResource(R.string.pref_sorting_method),
|
||||
currentOptions = SettingsRadioOptions(
|
||||
id = sortingMethod,
|
||||
text = SortingMethod.fromId(sortingMethod).getDisplayName(context)
|
||||
text = stringResource(SortingMethod.fromId(sortingMethod).nameRes)
|
||||
),
|
||||
options = sortingList,
|
||||
onSelect = { scope.launch { DataStoreManager.setSortingMethod(it) } },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -9,7 +8,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
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.SettingsItem
|
||||
|
||||
|
|
@ -20,53 +18,49 @@ fun SettingsMain(
|
|||
toInterfacePage: () -> Unit,
|
||||
toDataPage: () -> Unit,
|
||||
toAboutPage: () -> Unit,
|
||||
onNavigateUp: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TopAppBarScaffold(
|
||||
title = stringResource(R.string.page_settings),
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier
|
||||
) { innerPadding ->
|
||||
RoundedScreenContainer(Modifier.padding(innerPadding)) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_palette,
|
||||
title = stringResource(R.string.pref_appearance),
|
||||
description = stringResource(R.string.pref_appearance_desc),
|
||||
onClick = toAppearancePage,
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
) {
|
||||
SettingsContainer(Modifier.fillMaxSize()) {
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_palette,
|
||||
title = stringResource(R.string.pref_appearance),
|
||||
description = stringResource(R.string.pref_appearance_desc),
|
||||
onClick = toAppearancePage,
|
||||
topRounded = true
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_view_comfy,
|
||||
title = stringResource(R.string.pref_interface_interaction),
|
||||
description = stringResource(R.string.pref_interface_interaction_desc),
|
||||
onClick = toInterfacePage
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_view_comfy,
|
||||
title = stringResource(R.string.pref_interface_interaction),
|
||||
description = stringResource(R.string.pref_interface_interaction_desc),
|
||||
onClick = toInterfacePage
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_dns,
|
||||
title = stringResource(R.string.pref_data),
|
||||
description = stringResource(R.string.pref_data_desc),
|
||||
onClick = toDataPage
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_dns,
|
||||
title = stringResource(R.string.pref_data),
|
||||
description = stringResource(R.string.pref_data_desc),
|
||||
onClick = toDataPage
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_info,
|
||||
title = stringResource(R.string.pref_about),
|
||||
description = stringResource(R.string.pref_about_desc),
|
||||
onClick = toAboutPage,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
item {
|
||||
SettingsItem(
|
||||
leadingIconRes = R.drawable.ic_info,
|
||||
title = stringResource(R.string.pref_about),
|
||||
description = stringResource(R.string.pref_about_desc),
|
||||
onClick = toAboutPage,
|
||||
bottomRounded = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import androidx.compose.ui.semantics.semantics
|
|||
import androidx.compose.ui.semantics.stateDescription
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.theme.ContrastLevel
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
|
||||
@Composable
|
||||
|
|
@ -50,8 +50,7 @@ fun ContrastPicker(
|
|||
bottomRounded = bottomRounded,
|
||||
modifier = modifier
|
||||
) {
|
||||
val contrastLevelName =
|
||||
ContrastLevel.entries.map { it.getDisplayName(context) }
|
||||
val contrastLevelName = ContrastLevel.entries.map { stringResource(it.nameRes) }
|
||||
var lastVibratedLevel by remember { mutableFloatStateOf(currentContrast.value) }
|
||||
|
||||
Slider(
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.logic.model.DarkMode
|
||||
import cn.super12138.todo.ui.TodoDefaults
|
||||
import cn.super12138.todo.ui.pages.settings.components.LazyRowSettingsItem
|
||||
import cn.super12138.todo.ui.theme.DarkMode
|
||||
|
||||
@Composable
|
||||
fun DarkModePicker(
|
||||
|
|
@ -25,7 +24,6 @@ fun DarkModePicker(
|
|||
topRounded: Boolean = false,
|
||||
bottomRounded: Boolean = false
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val isInDarkTheme = isSystemInDarkTheme()
|
||||
|
||||
val darkModeList = remember { DarkMode.entries.toList() }
|
||||
|
|
@ -50,7 +48,7 @@ fun DarkModePicker(
|
|||
|
||||
DarkModeItem(
|
||||
iconRes = it.iconRes,
|
||||
name = it.getDisplayName(context),
|
||||
name = stringResource(it.nameRes),
|
||||
contentColor = contentColor,
|
||||
containerColor = containerColor,
|
||||
selected = isSelected,
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import cn.super12138.todo.ui.theme.ContrastLevel
|
||||
import cn.super12138.todo.ui.theme.PaletteStyle
|
||||
import cn.super12138.todo.logic.model.ContrastLevel
|
||||
import cn.super12138.todo.logic.model.PaletteStyle
|
||||
import cn.super12138.todo.ui.theme.dynamicColorScheme
|
||||
import cn.super12138.todo.utils.VibrationUtils
|
||||
|
||||
|
|
@ -44,7 +44,6 @@ fun PaletteItem(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -107,7 +106,7 @@ fun PaletteItem(
|
|||
Spacer(Modifier.size(8.dp))
|
||||
|
||||
Text(
|
||||
text = paletteStyle.getDisplayName(context),
|
||||
text = stringResource(paletteStyle.nameRes),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.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
|
||||
fun PalettePicker(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import androidx.compose.material3.MaterialExpressiveTheme
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import cn.super12138.todo.logic.model.PaletteStyle
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import cn.super12138.todo.logic.model.PaletteStyle
|
||||
import com.kyant.m3color.hct.Hct
|
||||
import com.kyant.m3color.scheme.SchemeContent
|
||||
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
|
||||
@Composable
|
||||
private fun Color.animate(animationSpec: AnimationSpec<Color> = spring()): Color {
|
||||
return animateColorAsState(this, animationSpec).value
|
||||
}
|
||||
private fun Color.animate(animationSpec: AnimationSpec<Color> = spring()): Color =
|
||||
animateColorAsState(this, animationSpec).value
|
||||
|
|
@ -5,12 +5,15 @@ import android.net.Uri
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import cn.super12138.todo.TodoApp
|
||||
import cn.super12138.todo.constants.Constants
|
||||
import cn.super12138.todo.logic.Repository
|
||||
import cn.super12138.todo.logic.database.TodoEntity
|
||||
import cn.super12138.todo.logic.datastore.DataStoreManager
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -32,6 +35,8 @@ import java.util.zip.ZipOutputStream
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class MainViewModel : ViewModel() {
|
||||
val topLevelBackStack = TopLevelBackStack<NavKey>(startKey = TodoScreen.Overview)
|
||||
|
||||
// 待办
|
||||
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||
val sortedTodos: Flow<List<TodoEntity>> =
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package cn.super12138.todo.utils
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
|
@ -39,4 +41,13 @@ object SystemUtils {
|
|||
|
||||
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
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_ballot.xml
Normal file
9
app/src/main/res/drawable/ic_ballot.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_ballot_filled.xml
Normal file
9
app/src/main/res/drawable/ic_ballot_filled.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_settings_filled.xml
Normal file
9
app/src/main/res/drawable/ic_settings_filled.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_view_kanban.xml
Normal file
9
app/src/main/res/drawable/ic_view_kanban.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_view_kanban_filled.xml
Normal file
9
app/src/main/res/drawable/ic_view_kanban_filled.xml
Normal 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>
|
||||
|
|
@ -115,4 +115,7 @@
|
|||
<string name="pref_developer_options">开发者选项</string>
|
||||
<string name="pref_developer_options_desc">一些调试选项,仅供开发使用</string>
|
||||
<string name="pref_padding">边距</string>
|
||||
<string name="tip_selected">已选择</string>
|
||||
<string name="page_tasks">待办</string>
|
||||
<string name="page_overview">概览</string>
|
||||
</resources>
|
||||
|
|
@ -117,4 +117,6 @@
|
|||
<string name="pref_developer_options_desc">Some debugging options, only for DEVELOPMENT use</string>
|
||||
<string name="pref_padding">Padding</string>
|
||||
<string name="tip_selected">Selected</string>
|
||||
<string name="page_tasks">Tasks</string>
|
||||
<string name="page_overview">Overview</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue