feat(settings): 支持自定义待办列表排序
This commit is contained in:
parent
47885dbf6c
commit
a0f385e91e
9 changed files with 214 additions and 14 deletions
|
|
@ -23,4 +23,7 @@ object Constants {
|
||||||
|
|
||||||
const val PREF_SHOW_COMPLETED = "show_completed"
|
const val PREF_SHOW_COMPLETED = "show_completed"
|
||||||
const val PREF_SHOW_COMPLETED_DEFAULT = true
|
const val PREF_SHOW_COMPLETED_DEFAULT = true
|
||||||
|
|
||||||
|
const val PREF_SORTING_METHOD = "sorting_method"
|
||||||
|
const val PREF_SORTING_METHOD_DEFAULT = 1
|
||||||
}
|
}
|
||||||
|
|
@ -4,23 +4,27 @@ import cn.super12138.todo.utils.SPDelegates
|
||||||
|
|
||||||
object GlobalValues {
|
object GlobalValues {
|
||||||
var dynamicColor: Boolean by SPDelegates(
|
var dynamicColor: Boolean by SPDelegates(
|
||||||
Constants.PREF_DYNAMIC_COLOR,
|
key = Constants.PREF_DYNAMIC_COLOR,
|
||||||
Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
||||||
)
|
)
|
||||||
var paletteStyle: Int by SPDelegates(
|
var paletteStyle: Int by SPDelegates(
|
||||||
Constants.PREF_PALETTE_STYLE,
|
key = Constants.PREF_PALETTE_STYLE,
|
||||||
Constants.PREF_PALETTE_STYLE_DEFAULT
|
default = Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||||
)
|
)
|
||||||
var darkMode: Int by SPDelegates(
|
var darkMode: Int by SPDelegates(
|
||||||
Constants.PREF_DARK_MODE,
|
key = Constants.PREF_DARK_MODE,
|
||||||
Constants.PREF_DARK_MODE_DEFAULT
|
default = Constants.PREF_DARK_MODE_DEFAULT
|
||||||
)
|
)
|
||||||
var contrastLevel: Float by SPDelegates(
|
var contrastLevel: Float by SPDelegates(
|
||||||
Constants.PREF_CONTRAST_LEVEL,
|
key = Constants.PREF_CONTRAST_LEVEL,
|
||||||
Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
default = Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
||||||
)
|
)
|
||||||
var showCompleted: Boolean by SPDelegates(
|
var showCompleted: Boolean by SPDelegates(
|
||||||
Constants.PREF_SHOW_COMPLETED,
|
key = Constants.PREF_SHOW_COMPLETED,
|
||||||
Constants.PREF_SHOW_COMPLETED_DEFAULT
|
default = Constants.PREF_SHOW_COMPLETED_DEFAULT
|
||||||
|
)
|
||||||
|
var sortingMethod: Int by SPDelegates(
|
||||||
|
key = Constants.PREF_SORTING_METHOD,
|
||||||
|
default = Constants.PREF_SORTING_METHOD_DEFAULT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.super12138.todo.logic.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import cn.super12138.todo.R
|
||||||
|
|
||||||
|
enum class SortingMethod(val id: Int) {
|
||||||
|
Date(1),
|
||||||
|
Priority(2),
|
||||||
|
Completion(3),
|
||||||
|
AlphabeticalAscending(4),
|
||||||
|
AlphabeticalDescending(5);
|
||||||
|
|
||||||
|
fun getDisplayName(context: Context): String {
|
||||||
|
val resId = when (this) {
|
||||||
|
Date -> R.string.sorting_date
|
||||||
|
Priority -> R.string.sorting_priority
|
||||||
|
Completion -> R.string.sorting_completion
|
||||||
|
AlphabeticalAscending -> R.string.sorting_alphabetical_ascending
|
||||||
|
AlphabeticalDescending -> R.string.sorting_alphabetical_descending
|
||||||
|
}
|
||||||
|
return context.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromId(id: Int): SortingMethod {
|
||||||
|
return SortingMethod.entries.find { it.id == id } ?: Date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
@ -37,7 +36,6 @@ import androidx.window.core.layout.WindowWidthSizeClass
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.constants.Constants
|
import cn.super12138.todo.constants.Constants
|
||||||
import cn.super12138.todo.logic.database.TodoEntity
|
import cn.super12138.todo.logic.database.TodoEntity
|
||||||
import cn.super12138.todo.ui.components.BasicDialog
|
|
||||||
import cn.super12138.todo.ui.components.WarningDialog
|
import cn.super12138.todo.ui.components.WarningDialog
|
||||||
import cn.super12138.todo.ui.pages.main.components.TodoFAB
|
import cn.super12138.todo.ui.pages.main.components.TodoFAB
|
||||||
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
|
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
|
||||||
|
|
@ -53,7 +51,7 @@ fun MainPage(
|
||||||
animatedVisibilityScope: AnimatedVisibilityScope,
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val toDoList = viewModel.toDos.collectAsState(initial = emptyList())
|
val toDoList = viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val isExpanded by remember {
|
val isExpanded by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,28 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Sort
|
||||||
import androidx.compose.material.icons.outlined.Checklist
|
import androidx.compose.material.icons.outlined.Checklist
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cn.super12138.todo.R
|
import cn.super12138.todo.R
|
||||||
import cn.super12138.todo.constants.Constants
|
import cn.super12138.todo.constants.Constants
|
||||||
|
import cn.super12138.todo.logic.model.SortingMethod
|
||||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
|
import cn.super12138.todo.ui.pages.settings.components.SettingsCategory
|
||||||
|
import cn.super12138.todo.ui.pages.settings.components.SettingsItem
|
||||||
|
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioDialog
|
||||||
|
import cn.super12138.todo.ui.pages.settings.components.SettingsRadioOptions
|
||||||
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
|
import cn.super12138.todo.ui.pages.settings.components.SwitchSettingsItem
|
||||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||||
|
|
||||||
|
|
@ -27,13 +38,16 @@ fun SettingsInterface(
|
||||||
onNavigateUp: () -> Unit,
|
onNavigateUp: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
LargeTopAppBarScaffold(
|
LargeTopAppBarScaffold(
|
||||||
title = stringResource(R.string.pref_interface),
|
title = stringResource(R.string.pref_interface),
|
||||||
onBack = onNavigateUp,
|
onBack = onNavigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -50,6 +64,32 @@ fun SettingsInterface(
|
||||||
description = stringResource(R.string.pref_show_completed_desc),
|
description = stringResource(R.string.pref_show_completed_desc),
|
||||||
onCheckedChange = { viewModel.setShowCompleted(it) },
|
onCheckedChange = { viewModel.setShowCompleted(it) },
|
||||||
)
|
)
|
||||||
|
SettingsItem(
|
||||||
|
leadingIcon = Icons.AutoMirrored.Outlined.Sort,
|
||||||
|
title = stringResource(R.string.pref_sorting_method),
|
||||||
|
description = viewModel.appSortingMethod.getDisplayName(context),
|
||||||
|
onClick = { showSortingMethodDialog = true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sortingList = remember {
|
||||||
|
SortingMethod.entries.map {
|
||||||
|
SettingsRadioOptions(
|
||||||
|
id = it.id,
|
||||||
|
text = it.getDisplayName(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsRadioDialog(
|
||||||
|
key = Constants.PREF_SORTING_METHOD,
|
||||||
|
defaultIndex = Constants.PREF_SORTING_METHOD_DEFAULT,
|
||||||
|
visible = showSortingMethodDialog,
|
||||||
|
title = stringResource(R.string.pref_sorting_method),
|
||||||
|
options = sortingList,
|
||||||
|
onSelect = { id ->
|
||||||
|
viewModel.setSortingMethod(SortingMethod.fromId(id))
|
||||||
|
},
|
||||||
|
onDismiss = { showSortingMethodDialog = false }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package cn.super12138.todo.ui.pages.settings.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cn.super12138.todo.ui.components.BasicDialog
|
||||||
|
import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsRadioDialog(
|
||||||
|
key: String,
|
||||||
|
defaultIndex: Int,
|
||||||
|
visible: Boolean,
|
||||||
|
title: String,
|
||||||
|
options: List<SettingsRadioOptions>,
|
||||||
|
onSelect: (id: Int) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
SettingsDialog(
|
||||||
|
visible = visible,
|
||||||
|
title = title,
|
||||||
|
text = {
|
||||||
|
var selectedItemIndex by rememberPrefIntState(key, defaultIndex)
|
||||||
|
// Modifier.selectableGroup() 用来确保无障碍功能运行正确
|
||||||
|
Column(Modifier.selectableGroup()) {
|
||||||
|
options.forEach { option ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp)
|
||||||
|
.selectable(
|
||||||
|
selected = option.id == selectedItemIndex,
|
||||||
|
onClick = {
|
||||||
|
selectedItemIndex = option.id
|
||||||
|
onSelect(option.id)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = option.id == selectedItemIndex,
|
||||||
|
onClick = null // 设置为 null 有利于屏幕阅读器
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = option.text,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SettingsRadioOptions(
|
||||||
|
val id: Int,
|
||||||
|
val text: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsDialog(
|
||||||
|
visible: Boolean,
|
||||||
|
title: String,
|
||||||
|
text: @Composable (() -> Unit)? = null,
|
||||||
|
onDismissRequest: () -> Unit = {},
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
BasicDialog(
|
||||||
|
visible = visible,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = text,
|
||||||
|
confirmButton = {},
|
||||||
|
dismissButton = {},
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -10,16 +10,28 @@ import cn.super12138.todo.logic.Repository
|
||||||
import cn.super12138.todo.logic.database.TodoEntity
|
import cn.super12138.todo.logic.database.TodoEntity
|
||||||
import cn.super12138.todo.logic.model.ContrastLevel
|
import cn.super12138.todo.logic.model.ContrastLevel
|
||||||
import cn.super12138.todo.logic.model.DarkMode
|
import cn.super12138.todo.logic.model.DarkMode
|
||||||
|
import cn.super12138.todo.logic.model.SortingMethod
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
// 待办
|
// 待办
|
||||||
val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
private val toDos: Flow<List<TodoEntity>> = Repository.getAllTodos()
|
||||||
|
var appSortingMethod by mutableStateOf(SortingMethod.fromId(GlobalValues.sortingMethod))
|
||||||
|
val sortedTodos: Flow<List<TodoEntity>> = toDos.map { list ->
|
||||||
|
when (appSortingMethod) {
|
||||||
|
SortingMethod.Date -> list.sortedBy { it.id }
|
||||||
|
SortingMethod.Priority -> list.sortedByDescending { it.priority } // 优先级高的在前
|
||||||
|
SortingMethod.Completion -> list.sortedBy { it.isCompleted } // 未完成的在前
|
||||||
|
SortingMethod.AlphabeticalAscending -> list.sortedBy { it.content }
|
||||||
|
SortingMethod.AlphabeticalDescending -> list.sortedByDescending { it.content }
|
||||||
|
}
|
||||||
|
}
|
||||||
val showConfetti = mutableStateOf(false)
|
val showConfetti = mutableStateOf(false)
|
||||||
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
@ -133,4 +145,8 @@ class MainViewModel : ViewModel() {
|
||||||
fun setShowCompleted(show: Boolean) {
|
fun setShowCompleted(show: Boolean) {
|
||||||
showCompletedTodos = show
|
showCompletedTodos = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSortingMethod(sortingMethod: SortingMethod) {
|
||||||
|
appSortingMethod = sortingMethod
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,4 +82,10 @@
|
||||||
<string name="pref_view_on_github">在 GitHub 上查看</string>
|
<string name="pref_view_on_github">在 GitHub 上查看</string>
|
||||||
<string name="pref_view_on_github_desc">查看源代码、提交错误报告和改进建议</string>
|
<string name="pref_view_on_github_desc">查看源代码、提交错误报告和改进建议</string>
|
||||||
<string name="tip_discard_changes">退出编辑后将无法找回你修改过的数据。确定退出编辑吗?</string>
|
<string name="tip_discard_changes">退出编辑后将无法找回你修改过的数据。确定退出编辑吗?</string>
|
||||||
|
<string name="sorting_date">日期</string>
|
||||||
|
<string name="sorting_priority">优先级</string>
|
||||||
|
<string name="sorting_completion">完成状态</string>
|
||||||
|
<string name="sorting_alphabetical_ascending">首字母(升序)</string>
|
||||||
|
<string name="sorting_alphabetical_descending">首字母(降序)</string>
|
||||||
|
<string name="pref_sorting_method">排序方式</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -83,4 +83,10 @@
|
||||||
<string name="pref_view_on_github">View On GitHub</string>
|
<string name="pref_view_on_github">View On GitHub</string>
|
||||||
<string name="pref_view_on_github_desc">View source code, submit bug reports, and improvement suggestions</string>
|
<string name="pref_view_on_github_desc">View source code, submit bug reports, and improvement suggestions</string>
|
||||||
<string name="tip_discard_changes">After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing?</string>
|
<string name="tip_discard_changes">After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing?</string>
|
||||||
|
<string name="sorting_date">Date</string>
|
||||||
|
<string name="sorting_priority">Priority</string>
|
||||||
|
<string name="sorting_completion">Completion</string>
|
||||||
|
<string name="sorting_alphabetical_ascending">Alphabetical (Ascending)</string>
|
||||||
|
<string name="sorting_alphabetical_descending">Alphabetical (Descending)</string>
|
||||||
|
<string name="pref_sorting_method">Sorting Method</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in a new issue