feat(tasks): P1 завершён — 3-state видимость коллекций в настройках + персист
TasksPrefsStore (SharedPreferences) хранит режим Скрыта/Видима/Авто по коллекции. TasksViewModel через Factory(context), setSmartMode персистит; шит настроек 3-state от VM (убраны фиктивные локальные remember). Панель реагирует через visibleKeys. Гейт compile+detekt+test зелёный. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1016,17 +1016,12 @@ fun TasksSettingsSheet(
|
|||||||
lists: List<TaskListItem>,
|
lists: List<TaskListItem>,
|
||||||
defaultListHref: String?,
|
defaultListHref: String?,
|
||||||
onDefaultListChange: (String?) -> Unit,
|
onDefaultListChange: (String?) -> Unit,
|
||||||
|
visibility: SmartListVisibility,
|
||||||
|
onSmartModeChange: (SmartListKey, SmartListMode) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onSwipeBack: () -> Unit = onDismiss,
|
onSwipeBack: () -> Unit = onDismiss,
|
||||||
) {
|
) {
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
val visibilityOptions = listOf("Показывать", "Скрывать")
|
|
||||||
var importantVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var todayVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var weekVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var allVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var currentVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var completedVisible by remember { mutableStateOf(visibilityOptions.first()) }
|
|
||||||
var defaultMenuOpen by remember { mutableStateOf(false) }
|
var defaultMenuOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
@@ -1099,23 +1094,15 @@ fun TasksSettingsSheet(
|
|||||||
|
|
||||||
HorizontalDivider(color = F7Colors.Border)
|
HorizontalDivider(color = F7Colors.Border)
|
||||||
|
|
||||||
SmartListSettingRow(serverUrl, "important-gray.svg", "Важное", importantVisible) {
|
Text("Видимость умных списков", fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||||
importantVisible = it
|
SmartListKey.entries.forEach { key ->
|
||||||
}
|
SmartListSettingRow(
|
||||||
SmartListSettingRow(serverUrl, "calendar-today-gray.svg", "Сегодня", todayVisible) {
|
serverUrl = serverUrl,
|
||||||
todayVisible = it
|
iconPath = key.icon,
|
||||||
}
|
title = key.title,
|
||||||
SmartListSettingRow(serverUrl, "calendar-nextweek-gray.svg", "На неделе", weekVisible) {
|
mode = visibility.mode(key),
|
||||||
weekVisible = it
|
onModeChange = { onSmartModeChange(key, it) },
|
||||||
}
|
)
|
||||||
SmartListSettingRow(serverUrl, "calendar-icon-gray.svg", "Все", allVisible) {
|
|
||||||
allVisible = it
|
|
||||||
}
|
|
||||||
SmartListSettingRow(serverUrl, "current-gray.svg", "Текущие", currentVisible) {
|
|
||||||
currentVisible = it
|
|
||||||
}
|
|
||||||
SmartListSettingRow(serverUrl, "check-done-gray.svg", "Завершенные", completedVisible) {
|
|
||||||
completedVisible = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,11 +1114,16 @@ private fun SmartListSettingRow(
|
|||||||
serverUrl: String,
|
serverUrl: String,
|
||||||
iconPath: String,
|
iconPath: String,
|
||||||
title: String,
|
title: String,
|
||||||
selected: String,
|
mode: SmartListMode,
|
||||||
onSelect: (String) -> Unit,
|
onModeChange: (SmartListMode) -> Unit,
|
||||||
) {
|
) {
|
||||||
var menuOpen by remember { mutableStateOf(false) }
|
var menuOpen by remember { mutableStateOf(false) }
|
||||||
val options = listOf("Показывать", "Скрывать")
|
val options = listOf(
|
||||||
|
SmartListMode.VISIBLE to "Показывать",
|
||||||
|
SmartListMode.HIDDEN to "Скрывать",
|
||||||
|
SmartListMode.AUTO to "Автоматически",
|
||||||
|
)
|
||||||
|
val currentLabel = options.firstOrNull { it.first == mode }?.second ?: "Автоматически"
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
AsyncImage(model = tasksAsset(serverUrl, iconPath), contentDescription = null, modifier = Modifier.size(18.dp))
|
AsyncImage(model = tasksAsset(serverUrl, iconPath), contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
@@ -1149,7 +1141,7 @@ private fun SmartListSettingRow(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Text(selected)
|
Text(currentLabel)
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = tasksAsset(serverUrl, "chevron-down-16.svg"),
|
model = tasksAsset(serverUrl, "chevron-down-16.svg"),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -1157,11 +1149,11 @@ private fun SmartListSettingRow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||||
options.forEach { option ->
|
options.forEach { (optionMode, label) ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(option) },
|
text = { Text(label) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onSelect(option)
|
onModeChange(optionMode)
|
||||||
menuOpen = false
|
menuOpen = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package ru.forbion.f7cloud.feature.tasks
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Персист настроек Задач в SharedPreferences (по образцу `CalendarPrefsStore`).
|
||||||
|
* Сейчас — режим видимости умных коллекций (3-state), переживает перезапуск.
|
||||||
|
*/
|
||||||
|
class TasksPrefsStore(context: Context) {
|
||||||
|
private val prefs = context.applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun getVisibility(): SmartListVisibility {
|
||||||
|
val modes = SmartListKey.entries.mapNotNull { key ->
|
||||||
|
val raw = prefs.getString(keyMode(key), null) ?: return@mapNotNull null
|
||||||
|
runCatching { SmartListMode.valueOf(raw) }.getOrNull()?.let { key to it }
|
||||||
|
}.toMap()
|
||||||
|
return SmartListVisibility.of(modes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMode(key: SmartListKey, mode: SmartListMode) {
|
||||||
|
prefs.edit().putString(keyMode(key), mode.name).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREFS = "f7_tasks_prefs"
|
||||||
|
|
||||||
|
private fun keyMode(key: SmartListKey) = "smart_mode::${key.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -22,6 +22,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import ru.forbion.f7cloud.core.auth.AuthSession
|
import ru.forbion.f7cloud.core.auth.AuthSession
|
||||||
@@ -38,7 +39,8 @@ fun TasksScreen(
|
|||||||
onOpenListConsumed: () -> Unit = {},
|
onOpenListConsumed: () -> Unit = {},
|
||||||
onUnauthorized: () -> Unit = {},
|
onUnauthorized: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val vm: TasksViewModel = viewModel()
|
val context = LocalContext.current
|
||||||
|
val vm: TasksViewModel = viewModel(factory = TasksViewModel.Factory(context))
|
||||||
val state by vm.state.collectAsState()
|
val state by vm.state.collectAsState()
|
||||||
// Родитель для создаваемой подзадачи (открывает диалог ввода названия).
|
// Родитель для создаваемой подзадачи (открывает диалог ввода названия).
|
||||||
var subtaskParent by remember { mutableStateOf<TaskItem?>(null) }
|
var subtaskParent by remember { mutableStateOf<TaskItem?>(null) }
|
||||||
@@ -156,6 +158,8 @@ fun TasksScreen(
|
|||||||
lists = state.lists,
|
lists = state.lists,
|
||||||
defaultListHref = state.defaultListHref,
|
defaultListHref = state.defaultListHref,
|
||||||
onDefaultListChange = vm::setDefaultList,
|
onDefaultListChange = vm::setDefaultList,
|
||||||
|
visibility = state.smartVisibility,
|
||||||
|
onSmartModeChange = vm::setSmartMode,
|
||||||
onDismiss = vm::closeSettings,
|
onDismiss = vm::closeSettings,
|
||||||
onSwipeBack = vm::closeSettings,
|
onSwipeBack = vm::closeSettings,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ru.forbion.f7cloud.feature.tasks
|
package ru.forbion.f7cloud.feature.tasks
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -88,9 +90,11 @@ data class TasksUiState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TasksViewModel(
|
class TasksViewModel(
|
||||||
|
context: Context,
|
||||||
private val repository: TasksRepository = TasksRepository(),
|
private val repository: TasksRepository = TasksRepository(),
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _state = MutableStateFlow(TasksUiState())
|
private val prefsStore = TasksPrefsStore(context.applicationContext)
|
||||||
|
private val _state = MutableStateFlow(TasksUiState(smartVisibility = prefsStore.getVisibility()))
|
||||||
val state: StateFlow<TasksUiState> = _state.asStateFlow()
|
val state: StateFlow<TasksUiState> = _state.asStateFlow()
|
||||||
|
|
||||||
fun load(session: AuthSession) {
|
fun load(session: AuthSession) {
|
||||||
@@ -174,8 +178,9 @@ class TasksViewModel(
|
|||||||
loadAllTasks(session)
|
loadAllTasks(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Сменить режим видимости умной коллекции (шит настроек). */
|
/** Сменить режим видимости умной коллекции (шит настроек) + персист. */
|
||||||
fun setSmartMode(key: SmartListKey, mode: SmartListMode) {
|
fun setSmartMode(key: SmartListKey, mode: SmartListMode) {
|
||||||
|
prefsStore.setMode(key, mode)
|
||||||
_state.update { it.copy(smartVisibility = it.smartVisibility.withMode(key, mode)) }
|
_state.update { it.copy(smartVisibility = it.smartVisibility.withMode(key, mode)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,4 +508,10 @@ class TasksViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Factory(private val context: Context) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||||
|
TasksViewModel(context.applicationContext) as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user