feat(settings): 支持自定义待办列表排序

This commit is contained in:
Super12138 2025-02-06 13:51:45 +08:00
parent 47885dbf6c
commit a0f385e91e
9 changed files with 214 additions and 14 deletions

View file

@ -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
} }

View file

@ -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
) )
} }

View file

@ -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
}
}
}

View file

@ -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 {

View file

@ -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 }
)
} }

View file

@ -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
)
}

View file

@ -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
}
} }

View file

@ -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>

View file

@ -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>