feat(tasks): P1 фундамент — 3-state видимость коллекций + разрешение по счётчикам
SmartListMode (HIDDEN/VISIBLE/AUTO) по семантике веб server-show 0/1/2. TasksSmartLists.counts()/visibleKeys() — сводка и разрешение видимых коллекций. 25 юнит-тестов зелёные. Далее: агрегация в VM + вывод в TasksListNavView. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,23 +29,36 @@ enum class SmartListKey(val title: String, val icon: String) {
|
||||
COMPLETED("Завершенные", "check-done-gray.svg"),
|
||||
}
|
||||
|
||||
/** Какие умные списки показывать в панели (шит «Параметры задач»). По умолчанию — все. */
|
||||
/**
|
||||
* Режим видимости умной коллекции в панели — как в веб-Tasks (server `show` 0/1/2):
|
||||
* [HIDDEN] всегда скрыта · [VISIBLE] всегда видима · [AUTO] видима только при count > 0.
|
||||
*/
|
||||
enum class SmartListMode { HIDDEN, VISIBLE, AUTO }
|
||||
|
||||
/**
|
||||
* Настройка видимости умных коллекций (шит «Параметры задач»). Хранит только НЕ-дефолтные
|
||||
* режимы — для компактной сериализации в SharedPreferences. Умолчание — [DEFAULT_MODE].
|
||||
*/
|
||||
data class SmartListVisibility(
|
||||
private val hidden: Set<SmartListKey> = emptySet(),
|
||||
private val modes: Map<SmartListKey, SmartListMode> = emptyMap(),
|
||||
) {
|
||||
fun isVisible(key: SmartListKey): Boolean = key !in hidden
|
||||
fun mode(key: SmartListKey): SmartListMode = modes[key] ?: DEFAULT_MODE
|
||||
|
||||
fun withVisible(key: SmartListKey, visible: Boolean): SmartListVisibility =
|
||||
SmartListVisibility(if (visible) hidden - key else hidden + key)
|
||||
fun withMode(key: SmartListKey, mode: SmartListMode): SmartListVisibility =
|
||||
SmartListVisibility(
|
||||
if (mode == DEFAULT_MODE) modes - key else modes + (key to mode),
|
||||
)
|
||||
|
||||
/** Ключи скрытых списков — для сериализации в SharedPreferences. */
|
||||
fun hiddenKeys(): Set<SmartListKey> = hidden
|
||||
/** Только заданные вручную (не-дефолтные) режимы — для персиста. */
|
||||
fun explicitModes(): Map<SmartListKey, SmartListMode> = modes
|
||||
|
||||
companion object {
|
||||
val ALL_VISIBLE = SmartListVisibility()
|
||||
/** Умолчание: AUTO (показывать при непустом счётчике) — сверить с сервером live. */
|
||||
val DEFAULT_MODE = SmartListMode.AUTO
|
||||
val DEFAULT = SmartListVisibility()
|
||||
|
||||
fun of(hidden: Collection<SmartListKey>): SmartListVisibility =
|
||||
SmartListVisibility(hidden.toSet())
|
||||
fun of(modes: Map<SmartListKey, SmartListMode>): SmartListVisibility =
|
||||
SmartListVisibility(modes.filterValues { it != DEFAULT_MODE })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +87,25 @@ object TasksSmartLists {
|
||||
fun count(key: SmartListKey, tasks: List<TaskItem>, today: LocalDate): Int =
|
||||
tasks.count { matches(key, it, today) }
|
||||
|
||||
/** Счётчики всех коллекций сразу (по агрегату задач всех списков). */
|
||||
fun counts(tasks: List<TaskItem>, today: LocalDate): Map<SmartListKey, Int> =
|
||||
SmartListKey.entries.associateWith { key -> count(key, tasks, today) }
|
||||
|
||||
/**
|
||||
* Коллекции для показа в панели (в порядке enum) с учётом режима видимости и счётчиков:
|
||||
* VISIBLE — всегда, AUTO — при count > 0, HIDDEN — никогда.
|
||||
*/
|
||||
fun visibleKeys(
|
||||
visibility: SmartListVisibility,
|
||||
counts: Map<SmartListKey, Int>,
|
||||
): List<SmartListKey> = SmartListKey.entries.filter { key ->
|
||||
when (visibility.mode(key)) {
|
||||
SmartListMode.VISIBLE -> true
|
||||
SmartListMode.HIDDEN -> false
|
||||
SmartListMode.AUTO -> (counts[key] ?: 0) > 0
|
||||
}
|
||||
}
|
||||
|
||||
/** Уже стартовала: нет start, либо start сегодня/в прошлом. */
|
||||
private fun TaskItem.hasStarted(today: LocalDate): Boolean {
|
||||
val start = startLocalDate() ?: return true
|
||||
|
||||
+54
-12
@@ -182,23 +182,65 @@ class TasksSmartListsTest {
|
||||
assertEquals(1, TasksSmartLists.count(SmartListKey.COMPLETED, tasks, today))
|
||||
}
|
||||
|
||||
// --- Видимость ---
|
||||
// --- Видимость (3-state: Скрыта/Видима/Авто) ---
|
||||
|
||||
@Test
|
||||
fun visibility_defaultAllVisible() {
|
||||
val v = SmartListVisibility.ALL_VISIBLE
|
||||
SmartListKey.entries.forEach { assertTrue(v.isVisible(it)) }
|
||||
fun visibility_defaultModeIsAuto() {
|
||||
val v = SmartListVisibility.DEFAULT
|
||||
SmartListKey.entries.forEach { assertEquals(SmartListMode.AUTO, v.mode(it)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun visibility_hideAndRestore() {
|
||||
val hidden = SmartListVisibility.ALL_VISIBLE.withVisible(SmartListKey.COMPLETED, false)
|
||||
assertFalse(hidden.isVisible(SmartListKey.COMPLETED))
|
||||
assertTrue(hidden.isVisible(SmartListKey.ALL))
|
||||
assertEquals(setOf(SmartListKey.COMPLETED), hidden.hiddenKeys())
|
||||
fun visibility_setAndClearMode() {
|
||||
val v = SmartListVisibility.DEFAULT
|
||||
.withMode(SmartListKey.COMPLETED, SmartListMode.HIDDEN)
|
||||
.withMode(SmartListKey.ALL, SmartListMode.VISIBLE)
|
||||
assertEquals(SmartListMode.HIDDEN, v.mode(SmartListKey.COMPLETED))
|
||||
assertEquals(SmartListMode.VISIBLE, v.mode(SmartListKey.ALL))
|
||||
// Возврат в дефолт (AUTO) — режим не хранится (компактный персист).
|
||||
val cleared = v.withMode(SmartListKey.ALL, SmartListMode.AUTO)
|
||||
assertEquals(SmartListMode.AUTO, cleared.mode(SmartListKey.ALL))
|
||||
assertEquals(setOf(SmartListKey.COMPLETED), cleared.explicitModes().keys)
|
||||
}
|
||||
|
||||
val restored = hidden.withVisible(SmartListKey.COMPLETED, true)
|
||||
assertTrue(restored.isVisible(SmartListKey.COMPLETED))
|
||||
assertTrue(restored.hiddenKeys().isEmpty())
|
||||
@Test
|
||||
fun visibleKeys_autoShowsOnlyNonEmpty() {
|
||||
val counts = mapOf(
|
||||
SmartListKey.IMPORTANT to 2,
|
||||
SmartListKey.TODAY to 0,
|
||||
SmartListKey.WEEK to 0,
|
||||
SmartListKey.ALL to 5,
|
||||
SmartListKey.CURRENT to 5,
|
||||
SmartListKey.COMPLETED to 0,
|
||||
)
|
||||
// Всё по умолчанию AUTO → показываем только непустые, в порядке enum.
|
||||
assertEquals(
|
||||
listOf(SmartListKey.IMPORTANT, SmartListKey.ALL, SmartListKey.CURRENT),
|
||||
TasksSmartLists.visibleKeys(SmartListVisibility.DEFAULT, counts),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun visibleKeys_visibleAlwaysHiddenNever() {
|
||||
val counts = SmartListKey.entries.associateWith { 0 }
|
||||
val v = SmartListVisibility.DEFAULT
|
||||
.withMode(SmartListKey.ALL, SmartListMode.VISIBLE) // видима даже при 0
|
||||
.withMode(SmartListKey.CURRENT, SmartListMode.HIDDEN)
|
||||
val visible = TasksSmartLists.visibleKeys(v, counts)
|
||||
assertEquals(listOf(SmartListKey.ALL), visible) // остальные AUTO+0 скрыты, CURRENT HIDDEN
|
||||
}
|
||||
|
||||
@Test
|
||||
fun counts_overMixedSet() {
|
||||
val tasks = listOf(
|
||||
task(uid = "a", priority = 2, due = today),
|
||||
task(uid = "b", status = "COMPLETED"),
|
||||
task(uid = "c"),
|
||||
)
|
||||
val counts = TasksSmartLists.counts(tasks, today)
|
||||
assertEquals(1, counts[SmartListKey.IMPORTANT])
|
||||
assertEquals(2, counts[SmartListKey.ALL]) // a, c незавершённые
|
||||
assertEquals(1, counts[SmartListKey.COMPLETED])
|
||||
assertEquals(2, counts[SmartListKey.CURRENT]) // a, c (без будущего старта)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user