chore(openspec): архивировать android-album-sort и синхронизировать основные спеки
android-client: +1 требование — выбор порядка карточек и направления, порядок охватывает весь список вместе с альбомами устройства.
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-16
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
См. `proposal.md` — мотивация. Ограничения, определяющие подход:
|
||||||
|
|
||||||
|
- Порядок задавался запросом `AlbumDao.observeAll()` (`ORDER BY updatedAt DESC, title ASC`) —
|
||||||
|
из UI его не изменить, а серверный порядок (`created_at DESC`) клиентом не использовался.
|
||||||
|
- Список смешанный: `AlbumListBuilder` собирал два блока — карточки альбомов устройства, а
|
||||||
|
под ними серверные (связанный серверный альбом скрывался, его показывала карточка
|
||||||
|
устройства).
|
||||||
|
- `DeviceAlbum` (MediaStore-бакет) содержит `name`, `itemCount`, `coverUri`, `relativePath`,
|
||||||
|
но **не содержит дат**; дата элемента есть у `DeviceMedia` (`dateTaken`, `dateModified`).
|
||||||
|
- `DeviceMediaMapping.albums` уже вычисляет «самый свежий элемент» бакета, чтобы выбрать
|
||||||
|
обложку, — эту же величину можно взять как дату изменения альбома устройства.
|
||||||
|
- Локальные настройки отображения живут в DataStore (`SettingsRepository`); на вебе аналоги —
|
||||||
|
плотность сетки и тема.
|
||||||
|
- Серверные `users.preferences` заменяются целиком, поэтому настройка отображения туда не
|
||||||
|
идёт (см. решение 1 прошлой редакции).
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Один порядок для всего списка, включая альбомы устройства, и обратный порядок к нему.
|
||||||
|
- Сохранять выбор на устройстве и не менять поведение по умолчанию.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Синхронизация выбора между устройствами.
|
||||||
|
- Сортировка на вебе и на сервере.
|
||||||
|
- Отдельные варианты «по дате создания»: у альбома устройства нет даты создания бакета.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
**1. Один общий список, а не два блока.**
|
||||||
|
Карточки альбомов устройства и серверных альбомов упорядочиваются вместе по выбранному
|
||||||
|
ключу; источник и раньше был виден по иконкам (карта памяти / облако), поэтому группировка
|
||||||
|
по источнику ничего не добавляла, а сортировка «по названию» в двух блоках выглядела
|
||||||
|
несортированной. Связанная пара остаётся одной карточкой устройства и участвует в порядке
|
||||||
|
как она.
|
||||||
|
Альтернатива — сортировать каждый блок отдельно, оставив устройство сверху: меньше правок,
|
||||||
|
но выбранный порядок не выполнялся бы для половины списка.
|
||||||
|
|
||||||
|
**2. Дата альбома устройства — самый свежий его элемент.**
|
||||||
|
`DeviceMediaMapping` уже выбирает самый свежий элемент для обложки; то же значение
|
||||||
|
становится датой изменения альбома и попадает в `DeviceAlbum`. Дополнительных запросов к
|
||||||
|
MediaStore не нужно — данные уже в курсоре.
|
||||||
|
Альтернативы: `MAX(date_modified)` отдельным запросом (лишний проход по провайдеру);
|
||||||
|
брать только `device_assets` из Room (там лишь файлы, которые приложение уже обрабатывало, —
|
||||||
|
дата была бы почти всегда пустой).
|
||||||
|
|
||||||
|
**3. Направление — отдельный флаг поверх ключа.**
|
||||||
|
Хранится пара «ключ + обратный порядок», а не шесть независимых вариантов: меню остаётся
|
||||||
|
коротким, а переключатель «Обратный порядок» относится к любому ключу. Натуральные
|
||||||
|
направления: время изменения и число фото — по убыванию, название — по возрастанию;
|
||||||
|
обратный порядок инвертирует выбранное.
|
||||||
|
Альтернатива — шесть пунктов меню: нагляднее, но перегружает панель.
|
||||||
|
|
||||||
|
**4. Сравнение — по общим ключам карточки, в сборщике списка.**
|
||||||
|
`AlbumListBuilder` строит единый компаратор по паре «заголовок / число элементов / дата
|
||||||
|
изменения», которую умеет выдать и карточка устройства, и серверная. Серверную дату
|
||||||
|
(`updatedAt`, ISO-8601) приводит к миллисекундам доменная функция; отсутствующая дата уходит
|
||||||
|
в конец. Сортировка остаётся в сборщике, а Room — хранилищем без собственного порядка.
|
||||||
|
Альтернатива — отдельный запрос на каждый режим в `AlbumDao`: размножение запросов и жёсткая
|
||||||
|
связь сортировки с кэшем.
|
||||||
|
|
||||||
|
**5. Хранение — узкий порт `AlbumSortPreference` поверх DataStore.**
|
||||||
|
Пара «ключ + направление» читается и пишется через `AlbumSortPreference`
|
||||||
|
(`SettingsRepository` его реализует). Узость важна не только стилистически: она позволила
|
||||||
|
тестам ViewModel не поднимать DataStore, из-за чего несколько его экземпляров на одном
|
||||||
|
файле в одной JVM встают на файловой блокировке.
|
||||||
|
Альтернатива — читать настройки из общего `SettingsState`: шире зависимость и та же
|
||||||
|
проблема в тестах.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **Смешение источников в одном списке** → карточка устройства и серверная карточка могут
|
||||||
|
стоять рядом; источник различается иконками, а связанная пара по-прежнему одна карточка.
|
||||||
|
- **Альбом устройства без элементов** (пустой бакет) не имеет даты → попадает в конец при
|
||||||
|
сортировке по времени изменения.
|
||||||
|
- **Разбор ISO-даты серверного альбома** может не удаться (неожиданный формат) → такая
|
||||||
|
карточка уходит в конец, а не ломает сравнение.
|
||||||
|
- **Настройка не переносится на другое устройство** → принято: это настройка отображения,
|
||||||
|
а не пользовательские данные.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Изменение аддитивное: новая пара настроек в DataStore со значением по умолчанию (по времени
|
||||||
|
изменения, без обратного порядка), схема Room и API не меняются. Откат — предыдущая версия
|
||||||
|
APK, при которой список снова упорядочен только по времени изменения, а альбомы устройства
|
||||||
|
закреплены сверху.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Список альбомов в Android-клиенте упорядочивался жёстко (`ORDER BY updatedAt DESC,
|
||||||
|
title ASC` в запросе Room), а серверный порядок процедура `refresh` и вовсе теряла:
|
||||||
|
`created_at` приходил в DTO и отбрасывался при записи в кэш. Пользователь не мог ни выбрать
|
||||||
|
порядок, ни развернуть его. Дополнительно выяснилось, что выбранный порядок должен
|
||||||
|
охватывать **весь** список: альбомы устройства и серверные карточки видны вместе, а не
|
||||||
|
живут двумя независимыми блоками.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Список альбомов получает выбор порядка: по времени изменения (по умолчанию), по названию
|
||||||
|
или по числу фото, плюс обратный порядок для выбранного варианта.
|
||||||
|
- Порядок применяется ко **всему списку**: карточки альбомов устройства и серверных
|
||||||
|
альбомов упорядочиваются вместе, а не двумя блоками. Связанная пара «устройство →
|
||||||
|
серверный альбом» остаётся одной карточкой устройства и участвует в общем порядке как она.
|
||||||
|
- Альбом устройства получает дату последнего изменения (самый свежий его элемент, как уже
|
||||||
|
делается при выборе обложки), поэтому вариант «по времени изменения» осмыслен и для него.
|
||||||
|
- Пара (вариант, направление) сохраняется локально на устройстве (DataStore) и применяется
|
||||||
|
при следующем запуске. Это настройка отображения: она не синхронизируется между
|
||||||
|
устройствами.
|
||||||
|
- Сортировка выполняется при сборке списка, а не запросом Room: кэш остаётся хранилищем без
|
||||||
|
собственного порядка, поэтому события реального времени не знают о выбранной сортировке.
|
||||||
|
- **BREAKING**: нет. Значения по умолчанию (по времени изменения, без обратного порядка)
|
||||||
|
сохраняют прежнее поведение.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
_Нет — поведение расширяет существующую способность `android-client`._
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `android-client`: добавляется выбор порядка карточек и направления с локальным
|
||||||
|
сохранением; фиксируется, что порядок охватывает весь список, включая альбомы устройства.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- **Код (Android):** `domain/model/AlbumSort`, `domain/model/DeviceAlbums` (дата изменения),
|
||||||
|
`data/media/MediaStoreDeviceSource` (дата в маппинге), `data/prefs/SettingsRepository`
|
||||||
|
(порт `AlbumSortPreference` и хранение пары), `ui/albums/AlbumListItem`
|
||||||
|
(единый компаратор), `ui/albums/AlbumsViewModel`, `ui/albums/AlbumsScreen` (меню и
|
||||||
|
переключатель).
|
||||||
|
- **Схема Room:** без изменений; новые колонки не добавляются.
|
||||||
|
- **API и бэкенд:** не затрагиваются.
|
||||||
|
- **Веб-клиент:** не затрагивается, своей сортировки у него нет.
|
||||||
|
- **Не затрагивается:** подписка на персональный поток и живое обновление списка — это
|
||||||
|
отдельный change (`android-live-albums`).
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Выбор сортировки списка альбомов
|
||||||
|
Приложение SHALL предоставлять выбор порядка карточек в списке альбомов: «по времени
|
||||||
|
изменения», «по названию» или «по числу фото», а также обратный порядок для выбранного
|
||||||
|
варианта. Выбор SHALL сохраняться локально на устройстве и применяться при следующем
|
||||||
|
запуске. Порядок SHALL применяться ко всему списку: карточки альбомов устройства и
|
||||||
|
серверных альбомов упорядочиваются вместе.
|
||||||
|
|
||||||
|
#### Scenario: Выбор варианта
|
||||||
|
- **WHEN** пользователь выбирает вариант сортировки
|
||||||
|
- **THEN** карточки списка перестраиваются в соответствии с выбором
|
||||||
|
|
||||||
|
#### Scenario: Обратный порядок
|
||||||
|
- **WHEN** пользователь включает обратный порядок
|
||||||
|
- **THEN** выбранный вариант применяется в обратном направлении
|
||||||
|
|
||||||
|
#### Scenario: Альбомы устройства
|
||||||
|
- **WHEN** выбрана любая сортировка
|
||||||
|
- **THEN** карточки альбомов устройства упорядочиваются вместе с серверными, а для варианта «по времени изменения» используется дата самого свежего элемента альбома устройства
|
||||||
|
|
||||||
|
#### Scenario: Сортировка по умолчанию
|
||||||
|
- **WHEN** пользователь ещё не выбирал сортировку
|
||||||
|
- **THEN** карточки упорядочены по времени изменения, самые свежие сверху, обратный порядок выключен
|
||||||
|
|
||||||
|
#### Scenario: Сохранение выбора
|
||||||
|
- **WHEN** пользователь перезапускает приложение
|
||||||
|
- **THEN** список открывается с сохранёнными вариантом и направлением
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Ключи сортировки и хранение выбора
|
||||||
|
|
||||||
|
- [x] 1.1 Перечисление `AlbumSort` (время изменения, название, число фото) с разбором сохранённого значения и дефолтом «по времени изменения»; проверено unit-тестами
|
||||||
|
- [x] 1.2 Хранить пару (ключ, обратный порядок) в DataStore и отдавать её узким портом `AlbumSortPreference`; проверить тестом дефолт (по времени изменения, без обратного порядка) и чтение сохранённой пары обратно
|
||||||
|
|
||||||
|
## 2. Дата альбома устройства
|
||||||
|
|
||||||
|
- [x] 2.1 Добавить альбому устройства дату последнего изменения — самый свежий его элемент, как при выборе обложки, — и заполнять её в маппинге MediaStore; проверить тестом маппинга, что дата соответствует самому свежему элементу
|
||||||
|
|
||||||
|
## 3. Единый порядок списка
|
||||||
|
|
||||||
|
- [x] 3.1 Сортировать один общий список карточек (альбомы устройства и серверные вместе) по выбранному ключу; проверить тестами `AlbumListBuilder`, что порядок соответствует ключу и что карточки обоих источников перемешаны, а не сгруппированы
|
||||||
|
- [x] 3.2 Обратный порядок инвертирует выбранный ключ; проверить тестом, что направление меняет порядок для каждого ключа, а отсутствие даты не ломает сравнение
|
||||||
|
- [x] 3.3 Связанная пара «устройство → серверный» участвует в общем порядке как карточка устройства; проверить тестом, что связанный серверный альбом не появляется отдельной записью и остаётся в общем порядке
|
||||||
|
|
||||||
|
## 4. Выбор в интерфейсе
|
||||||
|
|
||||||
|
- [x] 4.1 Пробросить пару (ключ, обратный порядок) в `AlbumsViewModel` и перестраивать список без перезапроса; проверить тестом ViewModel, что смена ключа и включение обратного порядка меняют порядок
|
||||||
|
- [x] 4.2 Меню сортировки: три ключа с отметкой текущего и переключатель «Обратный порядок»; проверить Compose-тестом, что выбор меняет порядок, а отметки следуют за выбором
|
||||||
|
- [x] 4.3 Обновить `android/README.md`: сортировка охватывает весь список, есть обратный порядок
|
||||||
|
|
||||||
|
## 5. Проверка
|
||||||
|
|
||||||
|
- [x] 5.1 Проверить на устройстве сохранение выбора между запусками: выбрать «по названию» вместе с обратным порядком, перезапустить экран и убедиться, что порядок сохранился
|
||||||
|
- [x] 5.2 Прогнать на финальной ревизии `cd android && ./gradlew test` и `cd android && ./gradlew :app:assembleDebug`; зафиксировать, что обе команды проходят
|
||||||
|
|
||||||
|
## Workflow follow-up
|
||||||
|
|
||||||
|
- Заархивировать change после прохождения ревью проекта (`openspec archive android-album-sort`).
|
||||||
|
- Проверить, что после архивации требование вошло в `openspec/specs/android-client/spec.md`, а `openspec validate --strict android-album-sort` не выдаёт ошибок.
|
||||||
@@ -671,3 +671,30 @@ SHALL показывать значками расположение содер
|
|||||||
#### Scenario: Отсутствие альбома в кэше без сети
|
#### Scenario: Отсутствие альбома в кэше без сети
|
||||||
- **WHEN** приложение открывает экран альбома, которого нет в кэше, и сеть недоступна
|
- **WHEN** приложение открывает экран альбома, которого нет в кэше, и сеть недоступна
|
||||||
- **THEN** экран не считается потерявшим доступ и не закрывается сам
|
- **THEN** экран не считается потерявшим доступ и не закрывается сам
|
||||||
|
|
||||||
|
### Requirement: Выбор сортировки списка альбомов
|
||||||
|
Приложение SHALL предоставлять выбор порядка карточек в списке альбомов: «по времени
|
||||||
|
изменения», «по названию» или «по числу фото», а также обратный порядок для выбранного
|
||||||
|
варианта. Выбор SHALL сохраняться локально на устройстве и применяться при следующем
|
||||||
|
запуске. Порядок SHALL применяться ко всему списку: карточки альбомов устройства и
|
||||||
|
серверных альбомов упорядочиваются вместе.
|
||||||
|
|
||||||
|
#### Scenario: Выбор варианта
|
||||||
|
- **WHEN** пользователь выбирает вариант сортировки
|
||||||
|
- **THEN** карточки списка перестраиваются в соответствии с выбором
|
||||||
|
|
||||||
|
#### Scenario: Обратный порядок
|
||||||
|
- **WHEN** пользователь включает обратный порядок
|
||||||
|
- **THEN** выбранный вариант применяется в обратном направлении
|
||||||
|
|
||||||
|
#### Scenario: Альбомы устройства
|
||||||
|
- **WHEN** выбрана любая сортировка
|
||||||
|
- **THEN** карточки альбомов устройства упорядочиваются вместе с серверными, а для варианта «по времени изменения» используется дата самого свежего элемента альбома устройства
|
||||||
|
|
||||||
|
#### Scenario: Сортировка по умолчанию
|
||||||
|
- **WHEN** пользователь ещё не выбирал сортировку
|
||||||
|
- **THEN** карточки упорядочены по времени изменения, самые свежие сверху, обратный порядок выключен
|
||||||
|
|
||||||
|
#### Scenario: Сохранение выбора
|
||||||
|
- **WHEN** пользователь перезапускает приложение
|
||||||
|
- **THEN** список открывается с сохранёнными вариантом и направлением
|
||||||
|
|||||||
Reference in New Issue
Block a user