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"),
|
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(
|
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 =
|
fun withMode(key: SmartListKey, mode: SmartListMode): SmartListVisibility =
|
||||||
SmartListVisibility(if (visible) hidden - key else hidden + key)
|
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 {
|
companion object {
|
||||||
val ALL_VISIBLE = SmartListVisibility()
|
/** Умолчание: AUTO (показывать при непустом счётчике) — сверить с сервером live. */
|
||||||
|
val DEFAULT_MODE = SmartListMode.AUTO
|
||||||
|
val DEFAULT = SmartListVisibility()
|
||||||
|
|
||||||
fun of(hidden: Collection<SmartListKey>): SmartListVisibility =
|
fun of(modes: Map<SmartListKey, SmartListMode>): SmartListVisibility =
|
||||||
SmartListVisibility(hidden.toSet())
|
SmartListVisibility(modes.filterValues { it != DEFAULT_MODE })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +87,25 @@ object TasksSmartLists {
|
|||||||
fun count(key: SmartListKey, tasks: List<TaskItem>, today: LocalDate): Int =
|
fun count(key: SmartListKey, tasks: List<TaskItem>, today: LocalDate): Int =
|
||||||
tasks.count { matches(key, it, today) }
|
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 сегодня/в прошлом. */
|
/** Уже стартовала: нет start, либо start сегодня/в прошлом. */
|
||||||
private fun TaskItem.hasStarted(today: LocalDate): Boolean {
|
private fun TaskItem.hasStarted(today: LocalDate): Boolean {
|
||||||
val start = startLocalDate() ?: return true
|
val start = startLocalDate() ?: return true
|
||||||
|
|||||||
+54
-12
@@ -182,23 +182,65 @@ class TasksSmartListsTest {
|
|||||||
assertEquals(1, TasksSmartLists.count(SmartListKey.COMPLETED, tasks, today))
|
assertEquals(1, TasksSmartLists.count(SmartListKey.COMPLETED, tasks, today))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Видимость ---
|
// --- Видимость (3-state: Скрыта/Видима/Авто) ---
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun visibility_defaultAllVisible() {
|
fun visibility_defaultModeIsAuto() {
|
||||||
val v = SmartListVisibility.ALL_VISIBLE
|
val v = SmartListVisibility.DEFAULT
|
||||||
SmartListKey.entries.forEach { assertTrue(v.isVisible(it)) }
|
SmartListKey.entries.forEach { assertEquals(SmartListMode.AUTO, v.mode(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun visibility_hideAndRestore() {
|
fun visibility_setAndClearMode() {
|
||||||
val hidden = SmartListVisibility.ALL_VISIBLE.withVisible(SmartListKey.COMPLETED, false)
|
val v = SmartListVisibility.DEFAULT
|
||||||
assertFalse(hidden.isVisible(SmartListKey.COMPLETED))
|
.withMode(SmartListKey.COMPLETED, SmartListMode.HIDDEN)
|
||||||
assertTrue(hidden.isVisible(SmartListKey.ALL))
|
.withMode(SmartListKey.ALL, SmartListMode.VISIBLE)
|
||||||
assertEquals(setOf(SmartListKey.COMPLETED), hidden.hiddenKeys())
|
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)
|
@Test
|
||||||
assertTrue(restored.isVisible(SmartListKey.COMPLETED))
|
fun visibleKeys_autoShowsOnlyNonEmpty() {
|
||||||
assertTrue(restored.hiddenKeys().isEmpty())
|
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