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_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 {
|
||||
var dynamicColor: Boolean by SPDelegates(
|
||||
Constants.PREF_DYNAMIC_COLOR,
|
||||
Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
||||
key = Constants.PREF_DYNAMIC_COLOR,
|
||||
default = Constants.PREF_DYNAMIC_COLOR_DEFAULT
|
||||
)
|
||||
var paletteStyle: Int by SPDelegates(
|
||||
Constants.PREF_PALETTE_STYLE,
|
||||
Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||
key = Constants.PREF_PALETTE_STYLE,
|
||||
default = Constants.PREF_PALETTE_STYLE_DEFAULT
|
||||
)
|
||||
var darkMode: Int by SPDelegates(
|
||||
Constants.PREF_DARK_MODE,
|
||||
Constants.PREF_DARK_MODE_DEFAULT
|
||||
key = Constants.PREF_DARK_MODE,
|
||||
default = Constants.PREF_DARK_MODE_DEFAULT
|
||||
)
|
||||
var contrastLevel: Float by SPDelegates(
|
||||
Constants.PREF_CONTRAST_LEVEL,
|
||||
Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
||||
key = Constants.PREF_CONTRAST_LEVEL,
|
||||
default = Constants.PREF_CONTRAST_LEVEL_DEFAULT
|
||||
)
|
||||
var showCompleted: Boolean by SPDelegates(
|
||||
Constants.PREF_SHOW_COMPLETED,
|
||||
Constants.PREF_SHOW_COMPLETED_DEFAULT
|
||||
key = Constants.PREF_SHOW_COMPLETED,
|
||||
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.outlined.Delete
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -37,7 +36,6 @@ import androidx.window.core.layout.WindowWidthSizeClass
|
|||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.constants.Constants
|
||||
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.pages.main.components.TodoFAB
|
||||
import cn.super12138.todo.ui.pages.main.components.TodoTopAppBar
|
||||
|
|
@ -53,7 +51,7 @@ fun MainPage(
|
|||
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val toDoList = viewModel.toDos.collectAsState(initial = emptyList())
|
||||
val toDoList = viewModel.sortedTodos.collectAsState(initial = emptyList())
|
||||
val listState = rememberLazyListState()
|
||||
val isExpanded by remember {
|
||||
derivedStateOf {
|
||||
|
|
|
|||
|
|
@ -6,17 +6,28 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Sort
|
||||
import androidx.compose.material.icons.outlined.Checklist
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
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.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
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.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.viewmodels.MainViewModel
|
||||
|
||||
|
|
@ -27,13 +38,16 @@ fun SettingsInterface(
|
|||
onNavigateUp: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) }
|
||||
LargeTopAppBarScaffold(
|
||||
title = stringResource(R.string.pref_interface),
|
||||
onBack = onNavigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { innerPadding ->
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -50,6 +64,32 @@ fun SettingsInterface(
|
|||
description = stringResource(R.string.pref_show_completed_desc),
|
||||
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.model.ContrastLevel
|
||||
import cn.super12138.todo.logic.model.DarkMode
|
||||
import cn.super12138.todo.logic.model.SortingMethod
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
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)
|
||||
var selectedEditTodo by mutableStateOf<TodoEntity?>(null)
|
||||
private set
|
||||
|
|
@ -133,4 +145,8 @@ class MainViewModel : ViewModel() {
|
|||
fun setShowCompleted(show: Boolean) {
|
||||
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_desc">查看源代码、提交错误报告和改进建议</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>
|
||||
|
|
@ -83,4 +83,10 @@
|
|||
<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="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>
|
||||
Loading…
Reference in a new issue