From e405dad8fac738fb5b47f504aa8ef872b0c3621c Mon Sep 17 00:00:00 2001 From: b-mob-tasks Date: Mon, 13 Jul 2026 05:54:53 +0000 Subject: [PATCH] =?UTF-8?q?feat(tasks):=20P1=20=D1=84=D1=83=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D0=BD=D1=82=20=E2=80=94=203-state=20=D0=B2?= =?UTF-8?q?=D0=B8=D0=B4=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D0=B9=20+=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SmartListMode (HIDDEN/VISIBLE/AUTO) по семантике веб server-show 0/1/2. TasksSmartLists.counts()/visibleKeys() — сводка и разрешение видимых коллекций. 25 юнит-тестов зелёные. Далее: агрегация в VM + вывод в TasksListNavView. Co-Authored-By: Claude Opus 4.8 --- .../f7cloud/feature/tasks/TasksSmartLists.kt | 52 ++++++++++++--- .../feature/tasks/TasksSmartListsTest.kt | 66 +++++++++++++++---- 2 files changed, 96 insertions(+), 22 deletions(-) diff --git a/feature/tasks/src/main/java/ru/forbion/f7cloud/feature/tasks/TasksSmartLists.kt b/feature/tasks/src/main/java/ru/forbion/f7cloud/feature/tasks/TasksSmartLists.kt index 2a0ff19..7665bd8 100644 --- a/feature/tasks/src/main/java/ru/forbion/f7cloud/feature/tasks/TasksSmartLists.kt +++ b/feature/tasks/src/main/java/ru/forbion/f7cloud/feature/tasks/TasksSmartLists.kt @@ -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 = emptySet(), + private val modes: Map = 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 = hidden + /** Только заданные вручную (не-дефолтные) режимы — для персиста. */ + fun explicitModes(): Map = modes companion object { - val ALL_VISIBLE = SmartListVisibility() + /** Умолчание: AUTO (показывать при непустом счётчике) — сверить с сервером live. */ + val DEFAULT_MODE = SmartListMode.AUTO + val DEFAULT = SmartListVisibility() - fun of(hidden: Collection): SmartListVisibility = - SmartListVisibility(hidden.toSet()) + fun of(modes: Map): SmartListVisibility = + SmartListVisibility(modes.filterValues { it != DEFAULT_MODE }) } } @@ -74,6 +87,25 @@ object TasksSmartLists { fun count(key: SmartListKey, tasks: List, today: LocalDate): Int = tasks.count { matches(key, it, today) } + /** Счётчики всех коллекций сразу (по агрегату задач всех списков). */ + fun counts(tasks: List, today: LocalDate): Map = + SmartListKey.entries.associateWith { key -> count(key, tasks, today) } + + /** + * Коллекции для показа в панели (в порядке enum) с учётом режима видимости и счётчиков: + * VISIBLE — всегда, AUTO — при count > 0, HIDDEN — никогда. + */ + fun visibleKeys( + visibility: SmartListVisibility, + counts: Map, + ): List = 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 diff --git a/feature/tasks/src/test/java/ru/forbion/f7cloud/feature/tasks/TasksSmartListsTest.kt b/feature/tasks/src/test/java/ru/forbion/f7cloud/feature/tasks/TasksSmartListsTest.kt index fa7ffdd..fd3e97c 100644 --- a/feature/tasks/src/test/java/ru/forbion/f7cloud/feature/tasks/TasksSmartListsTest.kt +++ b/feature/tasks/src/test/java/ru/forbion/f7cloud/feature/tasks/TasksSmartListsTest.kt @@ -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 (без будущего старта) } }