diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 501334f..95ef5c0 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt index 60d07f3..823b10c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt @@ -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 ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt new file mode 100644 index 0000000..9f31dbd --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/SortingMethod.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt index 7aa4e93..743a115 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt @@ -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 { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt index 743fab5..c3689ff 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt @@ -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 } + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt new file mode 100644 index 0000000..dda4819 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -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, + 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 + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt index 265601c..db49080 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/viewmodels/MainViewModel.kt @@ -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> = Repository.getAllTodos() + private val toDos: Flow> = Repository.getAllTodos() + var appSortingMethod by mutableStateOf(SortingMethod.fromId(GlobalValues.sortingMethod)) + val sortedTodos: Flow> = 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(null) private set @@ -133,4 +145,8 @@ class MainViewModel : ViewModel() { fun setShowCompleted(show: Boolean) { showCompletedTodos = show } + + fun setSortingMethod(sortingMethod: SortingMethod) { + appSortingMethod = sortingMethod + } } \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d42ec87..34c11fa 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -82,4 +82,10 @@ 在 GitHub 上查看 查看源代码、提交错误报告和改进建议 退出编辑后将无法找回你修改过的数据。确定退出编辑吗? + 日期 + 优先级 + 完成状态 + 首字母(升序) + 首字母(降序) + 排序方式 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afdf172..ef1cd49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -83,4 +83,10 @@ View On GitHub View source code, submit bug reports, and improvement suggestions After exiting edit mode, you will not be able to retrieve the data you have modified. Are you sure you want to exit editing? + Date + Priority + Completion + Alphabetical (Ascending) + Alphabetical (Descending) + Sorting Method \ No newline at end of file