diff --git a/openspec/changes/archive/2026-10-16-android-album-sort/.openspec.yaml b/openspec/changes/archive/2026-10-16-android-album-sort/.openspec.yaml new file mode 100644 index 0000000..0cb4765 --- /dev/null +++ b/openspec/changes/archive/2026-10-16-android-album-sort/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-16 diff --git a/openspec/changes/archive/2026-10-16-android-album-sort/design.md b/openspec/changes/archive/2026-10-16-android-album-sort/design.md new file mode 100644 index 0000000..e4eb96c --- /dev/null +++ b/openspec/changes/archive/2026-10-16-android-album-sort/design.md @@ -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, при которой список снова упорядочен только по времени изменения, а альбомы устройства +закреплены сверху. diff --git a/openspec/changes/archive/2026-10-16-android-album-sort/proposal.md b/openspec/changes/archive/2026-10-16-android-album-sort/proposal.md new file mode 100644 index 0000000..5a4ca27 --- /dev/null +++ b/openspec/changes/archive/2026-10-16-android-album-sort/proposal.md @@ -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`). diff --git a/openspec/changes/archive/2026-10-16-android-album-sort/specs/android-client/spec.md b/openspec/changes/archive/2026-10-16-android-album-sort/specs/android-client/spec.md new file mode 100644 index 0000000..fa467c1 --- /dev/null +++ b/openspec/changes/archive/2026-10-16-android-album-sort/specs/android-client/spec.md @@ -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** список открывается с сохранёнными вариантом и направлением diff --git a/openspec/changes/archive/2026-10-16-android-album-sort/tasks.md b/openspec/changes/archive/2026-10-16-android-album-sort/tasks.md new file mode 100644 index 0000000..6cbe2bc --- /dev/null +++ b/openspec/changes/archive/2026-10-16-android-album-sort/tasks.md @@ -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` не выдаёт ошибок. diff --git a/openspec/specs/android-client/spec.md b/openspec/specs/android-client/spec.md index 40fdea4..bf56509 100644 --- a/openspec/specs/android-client/spec.md +++ b/openspec/specs/android-client/spec.md @@ -671,3 +671,30 @@ SHALL показывать значками расположение содер #### Scenario: Отсутствие альбома в кэше без сети - **WHEN** приложение открывает экран альбома, которого нет в кэше, и сеть недоступна - **THEN** экран не считается потерявшим доступ и не закрывается сам + +### Requirement: Выбор сортировки списка альбомов +Приложение SHALL предоставлять выбор порядка карточек в списке альбомов: «по времени +изменения», «по названию» или «по числу фото», а также обратный порядок для выбранного +варианта. Выбор SHALL сохраняться локально на устройстве и применяться при следующем +запуске. Порядок SHALL применяться ко всему списку: карточки альбомов устройства и +серверных альбомов упорядочиваются вместе. + +#### Scenario: Выбор варианта +- **WHEN** пользователь выбирает вариант сортировки +- **THEN** карточки списка перестраиваются в соответствии с выбором + +#### Scenario: Обратный порядок +- **WHEN** пользователь включает обратный порядок +- **THEN** выбранный вариант применяется в обратном направлении + +#### Scenario: Альбомы устройства +- **WHEN** выбрана любая сортировка +- **THEN** карточки альбомов устройства упорядочиваются вместе с серверными, а для варианта «по времени изменения» используется дата самого свежего элемента альбома устройства + +#### Scenario: Сортировка по умолчанию +- **WHEN** пользователь ещё не выбирал сортировку +- **THEN** карточки упорядочены по времени изменения, самые свежие сверху, обратный порядок выключен + +#### Scenario: Сохранение выбора +- **WHEN** пользователь перезапускает приложение +- **THEN** список открывается с сохранёнными вариантом и направлением