feat(android): album/photo attribute-actions and device gallery sync fixes
Album panel and photo sheet with attributes/actions for every album (server and device), local album rename/cover/delete, EXIF attribute settings, and a unified device-gallery sync check: - add repository "Атрибуты и действия" panel (swipe left) on server and device albums: cover, info, navigation, rename/delete; album settings screen; photo sheet (swipe up) with info, tags and actions; EXIF attribute settings backed by profile preferences - local albums use local metadata and actions: app-only display name and cover (new device_album_meta table), delete removes photos from the phone via the system delete consent - unified mode x state policy (GallerySyncPolicy/SyncCheck) runs on start and on album open: upload/offload in move, restore originals in copy/off - fix move->copy restore, offload of any linked-but-local original, provenance recovery that no longer overwrites the album mode, serialized reconcile and duplicate-upload suppression; prompt for "all files" access on start - album card caption: name (with linked server name when different) and storage icons (memory card / cloud) with item count Sync delta specs into android-client and archive the change add-mobile-album-photo-actions. Verified: unit/Robolectric suite green, :app:assembleDebug, :app:lint, and the DeviceSyncE2ETest instrumented suite on the emulator (7/7).
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-13
|
||||
@@ -0,0 +1,129 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
См. `proposal.md` — зачем. Текущее состояние, влияющее на решение:
|
||||
|
||||
- `GaloApi` умеет только `albums list/get/create`, `photos list/get/upload`, `renamePhoto`,
|
||||
`deletePhoto`, `movePhotos`. Доступны, но не задействованы: `PATCH/DELETE /albums/:id`,
|
||||
`/albums/:id/members`, `/albums/:id/share`, `POST /photo-batch/copy`,
|
||||
`/photos/:id/tags`, `/tags`, `PATCH /auth/me/preferences`.
|
||||
- Сервер уже отдаёт в JSON фото `exif`, `tags`, `uploader`, `download_url`, а `/auth/me` —
|
||||
`preferences`; `PhotoDto`/`UserDto` в Android этих полей не читают.
|
||||
- `DeviceAlbumViewModel` держит `serverPhotoByMediaId` (сопоставление локального медиа
|
||||
серверному фото), но `DeviceViewerScreen` его не получает.
|
||||
- `DeviceAlbumScreen` уже использует `ModalNavigationDrawer` (стартовый/левый drawer) под
|
||||
настройки синхронизации.
|
||||
- Просмотр (`ViewerScreen`/`DeviceViewerScreen`) занят жестами: горизонтальный пейджер,
|
||||
pinch-zoom, double-tap, панорама (drag по двум осям), плеер видео.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Единая панель «Атрибуты и действия» на всех экранах альбомов (свайп справа налево).
|
||||
- Единый лист «Атрибуты и действия» в обоих просмотрах (свайп вверх).
|
||||
- Резолвинг источника атрибутов: сервер / связанный сервер / локальные значения.
|
||||
- Расширение клиентского API/DTO до паритета с web без изменений бэкенда.
|
||||
|
||||
**Non-Goals:**
|
||||
- Изменения бэкенда, web и схемы spec-ов (кроме этой delta).
|
||||
- Новые серверные способы работы с медиа (всё уже есть в API).
|
||||
- Пересмотр существующих жестов просмотра (пейджер, зум, панорама) и drawer'а синхронизации.
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Панель альбома = `ModalNavigationDrawer` с end-drawer
|
||||
Свайп справа налево открывает правый (end) `ModalNavigationDrawer` на `AlbumScreen` и на
|
||||
`DeviceAlbumScreen`. Так сохраняется симметрия: на device-экране левый drawer — синхронизация,
|
||||
правый — атрибуты/действия; на серверном — только правый.
|
||||
_Альтернатива_: bottom sheet на альбоме — не повторяет web-раскладку «правая панель» и хуже
|
||||
для длинного списка атрибутов.
|
||||
|
||||
### 2. Один компонент панели; атрибуты локального альбома — локальные
|
||||
Общий composable-контент панели принимает «модель атрибутов альбома», которую строит экран:
|
||||
|
||||
- серверный альбом → из `AlbumDto` (включая `role` из envelope);
|
||||
- локальный альбом → локальные метаданные (`DeviceAlbum` + app-only метаданные), связанный
|
||||
серверный альбом используется только как источник для навигации «Участники»/«Ссылки».
|
||||
|
||||
Секции панели показываются всегда; строки навигации, требующие серверной стороны, отображаются
|
||||
неактивными, когда связи нет.
|
||||
|
||||
_Альтернатива_: три отдельные панели — дублирование и расхождение поведения.
|
||||
|
||||
### 2a. App-only метаданные локального альбома
|
||||
Имя (отображаемое) и выбранная обложка локального альбома хранятся в новой Room-таблице
|
||||
`device_album_meta` (ключ — id альбома-устройства). «Переименовать» пишет `displayName`,
|
||||
«Выбрать обложку»/«Сбросить» — `coverUri`. Список альбомов и панель применяют их поверх
|
||||
данных MediaStore.
|
||||
|
||||
_Альтернатива_: менять bucket в MediaStore — невозможно без прав и необратимо.
|
||||
|
||||
### 2b. Удаление локального альбома удаляет медиа телефона
|
||||
«Удалить» собирает все URI медиа альбома и запрашивает системное согласие
|
||||
(`MediaStore.createDeleteRequest`) через тот же механизм, что и разгрузка в режиме C. После
|
||||
подтверждения система удаляет файлы; приложение забывает локальный индекс и метаданные
|
||||
(`forgetAlbum`).
|
||||
|
||||
### 3. Лист фото = `ModalBottomSheet`, открытие по порогу свайпа вверх
|
||||
`ViewerScreen` и `DeviceViewerScreen` добавляют `detectVerticalDragGestures` под условием:
|
||||
не открывать при активном зуме, панораме и поверх контролов видео. В счёт: открывать при
|
||||
`!zoomed && !isPanorama && kind != VIDEO` (для видео — через видимый drag-handle/кнопку).
|
||||
_Альтернатива_: кнопка вместо жеста — расходится с исходным требованием (свайп вверх).
|
||||
|
||||
### 4. Резолвинг атрибутов/действий device-медиа
|
||||
Если `serverPhotoByMediaId[mediaId]` есть — лист грузит серверное фото (`GET /photos/:id`) и
|
||||
показывает серверные атрибуты/действия; иначе — локальную модель из `DeviceMedia`.
|
||||
Сопоставление передаётся в `DeviceViewerScreen` из `DeviceAlbumViewModel`.
|
||||
|
||||
### 5. Расширение клиентского API/DTO (без бэкенда)
|
||||
- `PhotoDto` + `Photo`: `exif`, `tags`, `uploader`, `download_url`, `likes_count`,
|
||||
`comments_count` (используется то, что нужно листу).
|
||||
- `UserDto`/`me()`: `preferences`.
|
||||
- `GaloApi`: `updateAlbum` (title/description/cover/clear), `deleteAlbum`, `members`,
|
||||
share-links, `copyPhotos`, `addTag`/`removeTag`/`tags`, `download` (через существующий
|
||||
`download_url`), `savePreferences`.
|
||||
- `ProfileApi` не трогаем; `preferences` добавляется в ответ `me()`.
|
||||
|
||||
### 6. Настройки атрибутов — новый экран, состояние в профиле
|
||||
Переносим каталог `EXIF_LABELS`/`EXIF_BASE` (web `exif.ts`) в Android-ресурс/объект и храним
|
||||
`photo_info.visible` + `photo_info.labels` в `preferences` через `PATCH /auth/me/preferences`
|
||||
(тот же формат, что у web). _Альтернатива_: собственный формат — рассинхрон настроек между
|
||||
web и мобильным для одного пользователя.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Конфликт жеста «вверх» с зумом/панорамой/плеером] → открывать лист только вне этих
|
||||
состояний; для видео — не перехватывать жест, доступ через handle/кнопку.
|
||||
- [Удаление/переименование локального альбома не имеет смысла и деструктивно] → действия
|
||||
серверного уровня показываются только при связи и применяются к серверному альбому; без
|
||||
связи раздел действий пуст.
|
||||
- [Офлайн] → операции серверного уровня недоступны офлайн; используем уже существующее
|
||||
офлайн-состояние экрана и показываем ошибку без падения.
|
||||
- [Дублирование EXIF-каталога между web и Android] → переносим один в один и фиксируем
|
||||
базовый набор; расхождение контролируется тестом.
|
||||
- [Права доступа] → гейтим секции по `role` (владелец/админ/автор), как web.
|
||||
|
||||
### 7. Полная проверка синхронизации при запуске
|
||||
`SyncWorker` реконсилирует все известные галереи (связанные плюс имеющие локальный индекс),
|
||||
восстанавливает утраченные связи по меткам происхождения (случай частичной синхронизации) и
|
||||
перевыставляет незавершённые загрузки (`requeuePending`). Запуск воркера происходит после
|
||||
загрузки настроек (когда известен base URL), а смена режима на серверный сразу вызывает
|
||||
`reconcile`. Та же проверка выполняется при открытии галереи. Разгрузка в режиме переноса
|
||||
автоматическая: любой ассет, уже имеющий серверное фото и всё ещё хранящий оригинал на
|
||||
устройстве, разгружается (включая повторные попытки и ассеты, оставшиеся «ожидающими
|
||||
освобождения»). Обратный случай (переход из переноса в копии/выключено) возвращает оригиналы с
|
||||
server и довосстанавливает незавершённый возврат при следующей проверке; восстановление не
|
||||
обрывается на единичных ошибках. Решение по каждому ассету вынесено в политику
|
||||
`GallerySyncPolicy` («режим × состояние × связанность»), которую применяет общая проверка
|
||||
`SyncCheck` — так охвачены все сочетания, включая «копии с разгруженными файлами».
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Изменение аддитивное, бэкенд и данные не мигрируются. Выкатывается в составе обычного релиза
|
||||
Android-клиента; откат — откат версии приложения. Пользовательские `preferences` не меняют
|
||||
формат, поэтому web и мобильный остаются совместимы.
|
||||
|
||||
## Open Questions
|
||||
|
||||
Нет — все решения, влияющие на спек и декомпозицию, приняты выше.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
В web у альбомов и фото есть полноценное управление атрибутами и действиями (панель
|
||||
обложки/информации/навигации, разделы «Действия», «Метки», «Информация» с настройками
|
||||
EXIF-атрибутов). В Android-клиенте этого нет: экран альбома — только сетка, просмотр —
|
||||
только изображение с навигацией, а `GaloApi` умеет лишь переименовать/удалить/переместить.
|
||||
С телефона нельзя ни посмотреть свойства, ни выполнить действия «как в web».
|
||||
|
||||
## What Changes
|
||||
|
||||
- Новый **экранный жест**: свайп справа налево в любом альбоме (серверном и локальном)
|
||||
открывает панель **«Атрибуты и действия»** (drawer справа).
|
||||
- Панель альбома повторяет структуру web (`AlbumPanel` + экран настроек):
|
||||
- Атрибуты: «Обложка» (превью, выбрать/сбросить), «Информация» (название, описание,
|
||||
счётчики), «Навигация» (участники, настройки, ссылки для просмотра).
|
||||
- Действия: «Переименовать», «Удалить».
|
||||
- Источник данных: серверный альбом — данные сервера; локальный, связанный с серверным —
|
||||
реальные атрибуты связанного серверного альбома; локальный без связи — локальные
|
||||
значения (обложка/имя/счётчик), без раздела «Навигация».
|
||||
- Новый **жест просмотра**: свайп вверх в полноэкранном просмотре (серверном и device)
|
||||
открывает лист **«Атрибуты и действия»**, повторяющий web `PhotoPanel`:
|
||||
- Атрибуты: «Информация» (автор, файл, тип, разрешение, EXIF, снято, координаты,
|
||||
загружено) с настройками списка EXIF-атрибутов; «Метки».
|
||||
- Действия: «Открыть (просмотр)» (из сетки), «Скачать», «Переместить», «Копировать»,
|
||||
«Установить как обложку альбома», «Удалить»; переименование по тапу на имя.
|
||||
- Для device-медиа: если элемент сопоставлен серверному фото — реальные серверные
|
||||
атрибуты и действия; иначе локальные атрибуты и локальные действия.
|
||||
- Расширение клиентского слоя Android: `GaloApi` + DTO получают операции альбома
|
||||
(обновление/обложка/удаление/участники/share), копирование, метки, скачивание, `exif`
|
||||
и `preferences`; новый экран «Настройки атрибутов» (видимость + подписи, в профиль).
|
||||
- **Бэкенд не меняется**: все нужные endpoints уже есть.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
<!-- none -->
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `android-client`: добавляются требования к панели «Атрибуты и действия» на экранах
|
||||
альбомов, к листу «Атрибуты и действия» в полноэкранном просмотре, к выбору источника
|
||||
атрибутов для локальных альбомов/медиа и к поддерживающим операциям API/настройкам
|
||||
атрибутов в профиле.
|
||||
|
||||
## Impact
|
||||
|
||||
- `android/app/src/main/java/com/galo/android/`: `data/network/GaloApi.kt`, `.../dto/Dtos.kt`,
|
||||
`domain/model/`, `ui/album/`, `ui/viewer/`, `ui/devicealbum/`, новые экраны настроек.
|
||||
- Тесты Android (`android/app/src/androidTest`, `test`).
|
||||
- Бэкенд, web и схема spec-ов не затрагиваются (кроме новой delta для `android-client`).
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
# Spec Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Проверка доступа «Все файлы» при запуске
|
||||
Приложение SHALL при запуске, когда оно уже настроено и авторизовано, проверять наличие доступа
|
||||
«Все файлы» и при его отсутствии SHALL предлагать его включить, открывая системный экран настроек.
|
||||
|
||||
#### Scenario: Доступ не выдан
|
||||
- **WHEN** приложение запущено и доступ «Все файлы» не выдан
|
||||
- **THEN** показывается диалог с предложением включить доступ
|
||||
|
||||
#### Scenario: Включение доступа
|
||||
- **WHEN** пользователь подтверждает
|
||||
- **THEN** открывается системный экран выдачи доступа «Все файлы»
|
||||
|
||||
#### Scenario: Доступ уже выдан
|
||||
- **WHEN** доступ уже выдан
|
||||
- **THEN** диалог не показывается
|
||||
|
||||
### Requirement: Возврат оригиналов при выходе из переноса
|
||||
При переключении альбома из режима переноса в «копии» или «выключено» приложение SHALL
|
||||
возвращать оригиналы с сервера (когда пользователь выбирает возврат), а незавершённый возврат
|
||||
SHALL довосстанавливаться при следующей проверке (при запуске и при открытии альбома).
|
||||
|
||||
#### Scenario: Перенос → копии
|
||||
- **WHEN** пользователь переключает альбом из переноса в копии и подтверждает возврат оригиналов
|
||||
- **THEN** оригиналы скачиваются с сервера и заменяют превью на устройстве
|
||||
|
||||
#### Scenario: Довосстановление
|
||||
- **WHEN** альбом не в режиме переноса, но содержит превью с серверным фото
|
||||
- **THEN** при запуске или открытии альбома оригиналы довосстанавливаются
|
||||
|
||||
#### Scenario: Перенос → выключено
|
||||
- **WHEN** пользователь переключает перенос в выключено
|
||||
- **THEN** предлагается вернуть оригиналы или оставить превью
|
||||
|
||||
### Requirement: Единая проверка соответствия режиму
|
||||
При запуске и при открытии альбома приложение SHALL приводить локальное содержимое в
|
||||
соответствие режиму для всех сочетаний состояния ассета: в «переносе» разгружать уже загруженные
|
||||
оригиналы, а в «копиях»/«выключено» восстанавливать превью; отсутствующий локальный индекс SHALL
|
||||
восстанавливаться по метке происхождения.
|
||||
|
||||
#### Scenario: Копии с разгруженными файлами
|
||||
- **WHEN** альбом в режиме копий, а часть файлов разгружена (превью)
|
||||
- **THEN** проверка восстанавливает оригиналы
|
||||
|
||||
#### Scenario: Перенос с неразгруженными файлами
|
||||
- **WHEN** альбом в режиме переноса, файл есть на сервере, но оригинал ещё на телефоне
|
||||
- **THEN** проверка разгружает оригинал
|
||||
|
||||
#### Scenario: Потерянный индекс
|
||||
- **WHEN** у разгруженного медиа нет записи в локальном индексе
|
||||
- **THEN** связь и происхождение восстанавливаются по метке в файле
|
||||
|
||||
#### Scenario: Выключенный режим
|
||||
- **WHEN** альбом без синхронизации содержит превью
|
||||
- **THEN** оригиналы восстанавливаются, а сервер не затрагивается
|
||||
|
||||
### Requirement: Отсутствие дублей и гонок при синхронизации
|
||||
Проверка синхронизации SHALL выполняться последовательно (без одновременных проходов по одной
|
||||
галерее) и SHALL NOT ставить повторную загрузку, пока по тому же элементу уже есть задание в
|
||||
очереди. Метка происхождения в разгруженном файле SHALL хранить режим синхронизации, а не
|
||||
состояние элемента, чтобы восстановление не меняло режим галереи.
|
||||
|
||||
#### Scenario: Повторная проверка во время загрузки
|
||||
- **WHEN** проверка запускается снова, пока загрузка элемента ещё стоит в очереди
|
||||
- **THEN** повторная загрузка не ставится
|
||||
|
||||
#### Scenario: Метка происхождения хранит режим
|
||||
- **WHEN** разгруженный файл восстанавливается после переустановки
|
||||
- **THEN** режим галереи берётся из метки как режим (перенос/копии), а не как состояние элемента
|
||||
|
||||
### Requirement: Подпись карточки альбома
|
||||
Карточка альбома SHALL показывать в первой строке имя альбома, а для связанной пары — со
|
||||
стрелкой имя связанного серверного альбома, и только когда имена различаются. Вторая строка
|
||||
SHALL показывать значками расположение содержимого и число элементов: значок карты памяти для
|
||||
локального без синхронизации, значки карты памяти и облака для режима копий, значок облака для
|
||||
режима переноса; для серверного альбома — значок облака.
|
||||
|
||||
#### Scenario: Локальный без синхронизации
|
||||
- **WHEN** показан локальный альбом без синхронизации
|
||||
- **THEN** вторая строка показывает значок карты памяти и число элементов
|
||||
|
||||
#### Scenario: Режим копий
|
||||
- **WHEN** локальный альбом синхронизируется в режиме копий
|
||||
- **THEN** вторая строка показывает значки карты памяти и облака и число элементов
|
||||
|
||||
#### Scenario: Режим переноса
|
||||
- **WHEN** локальный альбом синхронизируется в режиме переноса
|
||||
- **THEN** вторая строка показывает значок облака и число элементов
|
||||
|
||||
#### Scenario: Разные имена связанной пары
|
||||
- **WHEN** имя локального альбома не совпадает с именем связанного серверного
|
||||
- **THEN** первая строка показывает «имя → имя серверного»
|
||||
|
||||
#### Scenario: Одинаковые имена
|
||||
- **WHEN** имена совпадают
|
||||
- **THEN** стрелка и имя серверного не показываются
|
||||
|
||||
### Requirement: Панель атрибутов и действий альбома
|
||||
Приложение SHALL открывать панель «Атрибуты и действия» по свайпу справа налево на экране
|
||||
любого альбома — серверного и локального. Панель SHALL повторять структуру web: разделы
|
||||
«Обложка», «Информация», «Навигация» и раздел «Действия».
|
||||
|
||||
#### Scenario: Открытие свайпом
|
||||
- **WHEN** пользователь свайпает справа налево в открытом альбоме
|
||||
- **THEN** открывается панель «Атрибуты и действия»
|
||||
|
||||
#### Scenario: Структура панели
|
||||
- **WHEN** панель открыта
|
||||
- **THEN** показаны разделы «Обложка», «Информация», «Навигация» и «Действия»
|
||||
|
||||
#### Scenario: Закрытие панели
|
||||
- **WHEN** пользователь закрывает панель (свайп в обратную сторону или касание вне панели)
|
||||
- **THEN** панель скрывается, сетка медиа снова доступна
|
||||
|
||||
### Requirement: Источник атрибутов локального альбома
|
||||
Для локального альбома приложение SHALL показывать ту же панель «Атрибуты и действия», что и
|
||||
для серверного, а метаданные SHALL брать локально (имя, число элементов, локальная обложка,
|
||||
режим синхронизации, связанный серверный альбом). Действия локального альбома SHALL
|
||||
применяться локально.
|
||||
|
||||
#### Scenario: Локальные метаданные
|
||||
- **WHEN** пользователь открывает панель локального альбома
|
||||
- **THEN** раздел «Информация» показывает локальные имя, число элементов и обложку
|
||||
|
||||
#### Scenario: Неактивная навигация без связи
|
||||
- **WHEN** локальный альбом не связан с серверным
|
||||
- **THEN** строки «Участники» и «Ссылки для просмотра» показаны, но неактивны
|
||||
|
||||
#### Scenario: Активная навигация при связи
|
||||
- **WHEN** локальный альбом связан с серверным
|
||||
- **THEN** строки «Участники» и «Ссылки для просмотра» активны и относятся к связанному серверному альбому
|
||||
|
||||
### Requirement: Переименование и обложка локального альбома
|
||||
Для локального альбома действие «Переименовать» SHALL менять отображаемое имя в приложении, не
|
||||
изменяя медиа или bucket на устройстве, а «Выбрать обложку»/«Сбросить» SHALL назначать или
|
||||
снимать обложку из медиа этого альбома. Имя и обложка SHALL храниться локально.
|
||||
|
||||
#### Scenario: Переименование
|
||||
- **WHEN** пользователь вводит новое имя локального альбома
|
||||
- **THEN** панель, заголовок и карточка в списке альбомов показывают новое имя
|
||||
|
||||
#### Scenario: Назначение и сброс обложки
|
||||
- **WHEN** пользователь выбирает медиа обложкой или сбрасывает её
|
||||
- **THEN** панель и карточка альбома отражают выбор
|
||||
|
||||
### Requirement: Удаление локального альбома с телефона
|
||||
Действие «Удалить» локального альбома SHALL после подтверждения удалять все фото и видео
|
||||
этого альбома с устройства, запрашивая при необходимости системное согласие.
|
||||
|
||||
#### Scenario: Подтверждение
|
||||
- **WHEN** пользователь выбирает «Удалить» локального альбома
|
||||
- **THEN** открывается диалог с предупреждением, что медиа будут удалены с телефона
|
||||
|
||||
#### Scenario: Удаление
|
||||
- **WHEN** пользователь подтверждает удаление
|
||||
- **THEN** медиа удаляются с устройства, а альбом исчезает из приложения
|
||||
|
||||
### Requirement: Атрибуты альбома
|
||||
Панель SHALL показывать обложку с выбором и сбросом, информацию (название, описание, число
|
||||
фото и участников) и переходы к участникам, настройкам и ссылкам для просмотра. Управление
|
||||
обложкой и раздел «Навигация» SHALL быть доступны только владельцу и администратору.
|
||||
|
||||
#### Scenario: Управление обложкой
|
||||
- **WHEN** владелец или администратор выбирает или сбрасывает обложку
|
||||
- **THEN** изменение сохраняется на сервере и отражается в списке и панели
|
||||
|
||||
#### Scenario: Наблюдатель
|
||||
- **WHEN** наблюдатель открывает панель
|
||||
- **THEN** управление обложкой не показывается, а строки «Настройки» и «Ссылки для просмотра» неактивны
|
||||
|
||||
#### Scenario: Переход к настройкам
|
||||
- **WHEN** владелец или администратор открывает настройки из раздела «Навигация»
|
||||
- **THEN** доступны правка названия и описания и управление ссылками для просмотра
|
||||
|
||||
### Requirement: Действия с альбомом
|
||||
Панель SHALL содержать действия «Переименовать» и «Удалить». Удаление SHALL подтверждаться
|
||||
диалогом, а удалённый альбом SHALL помещать свои фото в корзину.
|
||||
|
||||
#### Scenario: Переименование
|
||||
- **WHEN** владелец или администратор выбирает «Переименовать» и вводит новое название
|
||||
- **THEN** название сохраняется на сервере и отражается в списке и заголовке
|
||||
|
||||
#### Scenario: Удаление с подтверждением
|
||||
- **WHEN** владелец выбирает «Удалить» и подтверждает в диалоге
|
||||
- **THEN** альбом удаляется, а его фото попадают в корзину
|
||||
|
||||
### Requirement: Лист атрибутов и действий фото
|
||||
Полноэкранный просмотр SHALL открывать лист «Атрибуты и действия» по свайпу вверх как в
|
||||
серверном, так и в device-просмотре. Лист SHALL повторять структуру web: разделы
|
||||
«Информация», «Метки» и «Действия». При зуме изображения, панораме или активном управлении
|
||||
видео свайп вверх SHALL NOT открывать лист.
|
||||
|
||||
#### Scenario: Открытие листа
|
||||
- **WHEN** пользователь свайпает вверх во вписанном изображении
|
||||
- **THEN** открывается лист «Атрибуты и действия» для текущего элемента
|
||||
|
||||
#### Scenario: Зум или панорама
|
||||
- **WHEN** изображение увеличено или открыта панорама, и пользователь свайпает вверх
|
||||
- **THEN** лист не открывается, жест обрабатывается изображением
|
||||
|
||||
#### Scenario: Видео
|
||||
- **WHEN** открыт видеоэлемент, и пользователь взаимодействует с плеером
|
||||
- **THEN** свайп вверх не мешает управлению воспроизведением
|
||||
|
||||
### Requirement: Атрибуты фото в листе
|
||||
Раздел «Информация» SHALL показывать автора, размер файла, тип, разрешение, EXIF-атрибуты,
|
||||
дату съёмки, координаты и дату загрузки. Раздел «Метки» SHALL позволять добавлять и снимать
|
||||
метки с поиском по меткам пользователя и созданием новых.
|
||||
|
||||
#### Scenario: Информация
|
||||
- **WHEN** лист открыт
|
||||
- **THEN** раздел «Информация» показывает метаданные и EXIF текущего фото
|
||||
|
||||
#### Scenario: Метки
|
||||
- **WHEN** пользователь добавляет или снимает метку в разделе «Метки»
|
||||
- **THEN** изменение сразу отражается в просмотре без перезагрузки
|
||||
|
||||
### Requirement: Настройки списка атрибутов
|
||||
Раздел «Информация» SHALL давать доступ к настройкам со списком EXIF-атрибутов, где
|
||||
пользователь отмечает отображаемые атрибуты и задаёт их подписи, с действиями «отметить все»
|
||||
и «снять все». Настройки SHALL применяться ко всем фото и сохраняться в профиле пользователя.
|
||||
|
||||
#### Scenario: Открытие настроек
|
||||
- **WHEN** пользователь открывает настройки атрибутов
|
||||
- **THEN** показан список EXIF-атрибутов с галочками видимости и полями подписей
|
||||
|
||||
#### Scenario: Сохранение настроек
|
||||
- **WHEN** пользователь отмечает атрибуты, задаёт подписи и сохраняет
|
||||
- **THEN** настройки сохраняются в профиле и применяются в разделе «Информация»
|
||||
|
||||
#### Scenario: Сброс настроек
|
||||
- **WHEN** пользователь сбрасывает настройки
|
||||
- **THEN** применяется базовый набор атрибутов с исходными подписями
|
||||
|
||||
### Requirement: Действия с фото в листе
|
||||
Раздел «Действия» SHALL содержать «Скачать», «Переместить», «Копировать», «Установить как
|
||||
обложку альбома» (по правам) и «Удалить»; из сетки также «Открыть (просмотр)». Удаление SHALL
|
||||
подтверждаться диалогом. Переименование SHALL выполняться по тапу на имя файла.
|
||||
|
||||
#### Scenario: Состав действий
|
||||
- **WHEN** лист открыт
|
||||
- **THEN** показаны действия «Скачать», «Переместить», «Копировать», «Установить как обложку» и «Удалить» по правам
|
||||
|
||||
#### Scenario: Удаление фото
|
||||
- **WHEN** пользователь выбирает «Удалить» и подтверждает
|
||||
- **THEN** фото уходит в корзину, а просмотр переходит к соседнему элементу
|
||||
|
||||
#### Scenario: Переименование фото
|
||||
- **WHEN** автор, владелец или администратор меняет имя файла на непустое
|
||||
- **THEN** новое имя сохраняется и отражается в просмотре и сетке
|
||||
|
||||
### Requirement: Источник атрибутов device-медиа
|
||||
Для device-медиа приложение SHALL показывать атрибуты и действия сопоставленного серверного
|
||||
фото, когда сопоставление есть, и локальные атрибуты и действия (имя, размер, тип,
|
||||
разрешение, дата, путь) при его отсутствии.
|
||||
|
||||
#### Scenario: Сопоставлено серверному фото
|
||||
- **WHEN** device-медиа сопоставлено серверному фото
|
||||
- **THEN** лист показывает серверные атрибуты и серверные действия этого фото
|
||||
|
||||
#### Scenario: Не сопоставлено
|
||||
- **WHEN** сопоставления с серверным фото нет
|
||||
- **THEN** лист показывает локальные атрибуты и локальные действия элемента
|
||||
@@ -0,0 +1,65 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Клиентский API и DTO
|
||||
|
||||
- [x] 1.1 Добавить в `PhotoDto`/`Photo` поля `exif`, `tags`, `uploader`, `download_url` и разобрать их в маппере; проверить unit-тестом десериализации ответа `GET /photos/:id`
|
||||
- [x] 1.2 Добавить `preferences` в ответ `me()` (`UserDto`/DTO профиля); проверить unit-тестом, что сохранённые `photo_info` читаются
|
||||
- [x] 1.3 Добавить в `GaloApi` обновление альбома (`PATCH /albums/:id` с `title`/`description`/`cover_photo_id`/`clear_cover`), удаление (`DELETE`), список участников и share-ссылки; проверить unit-тестами запросов
|
||||
- [x] 1.4 Добавить в `GaloApi` копирование (`POST /photo-batch/copy`), метки (`POST/DELETE /photos/:id/tags`, `GET /tags?q=`) и сохранение профиля (`PATCH /auth/me/preferences`); проверить unit-тестами
|
||||
- [x] 1.5 Обновить `android/README.md` разделом о новых операциях API и проверить, что приведённые команды/примеры соответствуют коду
|
||||
|
||||
## 2. Модель атрибутов и резолвинг источника
|
||||
|
||||
- [x] 2.1 Ввести модель «атрибуты альбома» и резолвер источник→модель (серверный / локальный связанный / локальный без связи) отдельным классом домена; проверить unit-тестами все три случая
|
||||
- [x] 2.2 Передавать сопоставление device-медиа→серверное фото (`serverPhotoByMediaId`) в `DeviceViewerScreen`; проверить unit-тестом, что элемент без сопоставления получает локальный источник, а со сопоставлением — серверный
|
||||
|
||||
## 3. Панель «Атрибуты и действия» на экранах альбомов
|
||||
|
||||
- [x] 3.1 Добавить на `AlbumScreen` end-drawer, открываемый свайпом справа налево, с общим контентом панели; проверить androidTest-жестом открытия и закрытия
|
||||
- [x] 3.2 Добавить такой же end-drawer на `DeviceAlbumScreen`, не меняя левый drawer настроек синхронизации; проверить androidTest, что оба drawer'а открываются независимо
|
||||
- [x] 3.3 Реализовать разделы «Обложка», «Информация», «Навигация» с гейтингом по роли (владелец/админ); проверить androidTest: у наблюдателя нет управления обложкой и навигации
|
||||
- [x] 3.4 Реализовать действия «Переименовать» и «Удалить» с диалогом подтверждения и постановкой фото в корзину; проверить androidTest обоих сценариев
|
||||
- [x] 3.5 Добавить экран настроек альбома (правка названия/описания, список участников, управление share-ссылками) и переход к нему из «Навигации»; проверить androidTest сохранения названия и создания/отзыва ссылки
|
||||
|
||||
## 4. Лист «Атрибуты и действия» в просмотре
|
||||
|
||||
- [x] 4.1 Добавить `ModalBottomSheet` в `ViewerScreen` и `DeviceViewerScreen` с открытием по свайпу вверх и условием «не открывать при зуме, панораме и активном видео»; проверить androidTest: при зуме/панораме лист не открывается
|
||||
- [x] 4.2 Реализовать раздел «Информация» (автор, файл, тип, разрешение, EXIF, снято, координаты, загружено) и раздел «Метки» с добавлением/снятием и поиском; проверить androidTest появления метки и обновления без перезагрузки
|
||||
- [x] 4.3 Реализовать действия «Скачать», «Переместить», «Копировать», «Установить как обложку», «Удалить» (по правам) и переименование по тапу на имя; проверить androidTest каждого действия, включая подтверждение удаления
|
||||
|
||||
## 5. Настройки списка атрибутов
|
||||
|
||||
- [x] 5.1 Перенести каталог EXIF-атрибутов и базовый набор (эквивалент web `exif.ts`) в Android; проверить unit-тестом, что базовый набор и подписи совпадают с web
|
||||
- [x] 5.2 Реализовать экран настроек атрибутов (галочки видимости, поля подписей, «отметить все»/«снять все») и сохранение/сброс через `preferences`; проверить androidTest сохранения и сброса настроек
|
||||
|
||||
## 6. Интеграция и проверка
|
||||
|
||||
- [x] 6.1 Прогнать сквозные сценарии: серверный альбом (панель+настройки), device-альбом связанный и без связи, просмотр серверный и device (лист, метки, действия); проверить androidTest-сценариями
|
||||
- [x] 6.2 Собрать и прогнать статический анализ: `./gradlew :app:assembleDebug :app:lint :app:testDebugUnitTest` без ошибок
|
||||
|
||||
## 7. Локальные атрибуты и действия (все альбомы)
|
||||
|
||||
- [x] 7.1 Добавить app-only метаданные локального альбома (Room `device_album_meta`: отображаемое имя и обложка) и применять их в панели и списке альбомов; проверить unit- и Robolectric-тестами
|
||||
- [x] 7.2 Показывать раздел «Навигация» у всех альбомов; строки «Участники»/«Ссылки» активны только при связи с серверным альбомом, иначе неактивны; проверить тестом
|
||||
- [x] 7.3 Реализовать «Переименовать» (отображаемое имя в приложении) и локальную обложку (выбор из медиа альбома/сброс) для локального альбома; проверить тестами
|
||||
- [x] 7.4 Реализовать «Удалить» локального альбома: подтверждение + удаление медиа с телефона через системное согласие (`MediaStore.createDeleteRequest`); проверить тестами
|
||||
|
||||
## 8. Полная проверка синхронизации
|
||||
|
||||
- [x] 8.1 `SyncWorker` реконсилирует все известные галереи (связанные + с локальным индексом), восстанавливает частичные связи через `recover` и перевыставляет незавершённые загрузки; проверить unit-тестами (`reconcileAll`, `indexedAlbumIds`)
|
||||
- [x] 8.2 Запускать стартовую проверку после загрузки настроек (base URL) и вызывать `reconcile` при смене режима на серверный (не создавая связь с пустым серверным альбомом); проверить сборкой и тестами
|
||||
- [x] 8.3 Разгрузка в режиме переноса выполняется автоматически после загрузки и повторно на следующем проходе (без баннера и отдельных запросов согласия); проверить unit-тестом повтора `AWAITING_FREE`
|
||||
- [x] 8.4 Проверять доступ «Все файлы» при запуске и предлагать включить (диалог + системный экран); проверить Robolectric-тестом
|
||||
- [x] 8.5 Проверка и исправление при открытии галереи: любой ассет, уже загруженный на сервер, но всё ещё с оригиналом на телефоне, разгружается в режиме переноса; проверить unit-тестами
|
||||
- [x] 8.6 Возврат оригиналов при перенос→копии/выключено: восстановление не обрывается на единичных ошибках + довосстановление при запуске и открытии альбома; проверить тестами (`restoreTargets`, матрица переходов)
|
||||
- [x] 8.7 Единая политика и проверка «режим × состояние» (`GallerySyncPolicy`/`SyncCheck`) с восстановлением индекса по метке; применяется при запуске и открытии альбома; проверить тестами на все сочетания
|
||||
- [x] 8.8 Устранить гонки и дубли: сериализация `reconcile`, пропуск повторной загрузки при наличии задания в очереди, метка происхождения хранит режим (не состояние); проверить unit-тестами и инструментальными E2E на эмуляторе
|
||||
|
||||
## 9. Подпись карточки альбома
|
||||
|
||||
- [x] 9.1 Переделать подпись карточки: первая строка — имя и «→ имя серверного» только при различии имён; вторая строка — значки расположения (карта памяти / карта памяти + облако / облако) и число элементов; проверить Robolectric-тестами
|
||||
|
||||
## Workflow follow-up
|
||||
|
||||
- Архивация change после прохождения ревью проекта.
|
||||
- Проверка результата архивации.
|
||||
@@ -356,3 +356,266 @@ SHALL выполняться свайпом.
|
||||
|
||||
- **WHEN** оригинал загружен или недоступен
|
||||
- **THEN** индикатор загрузки скрыт
|
||||
|
||||
### Requirement: Проверка доступа «Все файлы» при запуске
|
||||
Приложение SHALL при запуске, когда оно уже настроено и авторизовано, проверять наличие доступа
|
||||
«Все файлы» и при его отсутствии SHALL предлагать его включить, открывая системный экран настроек.
|
||||
|
||||
#### Scenario: Доступ не выдан
|
||||
- **WHEN** приложение запущено и доступ «Все файлы» не выдан
|
||||
- **THEN** показывается диалог с предложением включить доступ
|
||||
|
||||
#### Scenario: Включение доступа
|
||||
- **WHEN** пользователь подтверждает
|
||||
- **THEN** открывается системный экран выдачи доступа «Все файлы»
|
||||
|
||||
#### Scenario: Доступ уже выдан
|
||||
- **WHEN** доступ уже выдан
|
||||
- **THEN** диалог не показывается
|
||||
|
||||
### Requirement: Возврат оригиналов при выходе из переноса
|
||||
При переключении альбома из режима переноса в «копии» или «выключено» приложение SHALL
|
||||
возвращать оригиналы с сервера (когда пользователь выбирает возврат), а незавершённый возврат
|
||||
SHALL довосстанавливаться при следующей проверке (при запуске и при открытии альбома).
|
||||
|
||||
#### Scenario: Перенос → копии
|
||||
- **WHEN** пользователь переключает альбом из переноса в копии и подтверждает возврат оригиналов
|
||||
- **THEN** оригиналы скачиваются с сервера и заменяют превью на устройстве
|
||||
|
||||
#### Scenario: Довосстановление
|
||||
- **WHEN** альбом не в режиме переноса, но содержит превью с серверным фото
|
||||
- **THEN** при запуске или открытии альбома оригиналы довосстанавливаются
|
||||
|
||||
#### Scenario: Перенос → выключено
|
||||
- **WHEN** пользователь переключает перенос в выключено
|
||||
- **THEN** предлагается вернуть оригиналы или оставить превью
|
||||
|
||||
### Requirement: Единая проверка соответствия режиму
|
||||
При запуске и при открытии альбома приложение SHALL приводить локальное содержимое в
|
||||
соответствие режиму для всех сочетаний состояния ассета: в «переносе» разгружать уже загруженные
|
||||
оригиналы, а в «копиях»/«выключено» восстанавливать превью; отсутствующий локальный индекс SHALL
|
||||
восстанавливаться по метке происхождения.
|
||||
|
||||
#### Scenario: Копии с разгруженными файлами
|
||||
- **WHEN** альбом в режиме копий, а часть файлов разгружена (превью)
|
||||
- **THEN** проверка восстанавливает оригиналы
|
||||
|
||||
#### Scenario: Перенос с неразгруженными файлами
|
||||
- **WHEN** альбом в режиме переноса, файл есть на сервере, но оригинал ещё на телефоне
|
||||
- **THEN** проверка разгружает оригинал
|
||||
|
||||
#### Scenario: Потерянный индекс
|
||||
- **WHEN** у разгруженного медиа нет записи в локальном индексе
|
||||
- **THEN** связь и происхождение восстанавливаются по метке в файле
|
||||
|
||||
#### Scenario: Выключенный режим
|
||||
- **WHEN** альбом без синхронизации содержит превью
|
||||
- **THEN** оригиналы восстанавливаются, а сервер не затрагивается
|
||||
|
||||
### Requirement: Отсутствие дублей и гонок при синхронизации
|
||||
Проверка синхронизации SHALL выполняться последовательно (без одновременных проходов по одной
|
||||
галерее) и SHALL NOT ставить повторную загрузку, пока по тому же элементу уже есть задание в
|
||||
очереди. Метка происхождения в разгруженном файле SHALL хранить режим синхронизации, а не
|
||||
состояние элемента, чтобы восстановление не меняло режим галереи.
|
||||
|
||||
#### Scenario: Повторная проверка во время загрузки
|
||||
- **WHEN** проверка запускается снова, пока загрузка элемента ещё стоит в очереди
|
||||
- **THEN** повторная загрузка не ставится
|
||||
|
||||
#### Scenario: Метка происхождения хранит режим
|
||||
- **WHEN** разгруженный файл восстанавливается после переустановки
|
||||
- **THEN** режим галереи берётся из метки как режим (перенос/копии), а не как состояние элемента
|
||||
|
||||
### Requirement: Подпись карточки альбома
|
||||
Карточка альбома SHALL показывать в первой строке имя альбома, а для связанной пары — со
|
||||
стрелкой имя связанного серверного альбома, и только когда имена различаются. Вторая строка
|
||||
SHALL показывать значками расположение содержимого и число элементов: значок карты памяти для
|
||||
локального без синхронизации, значки карты памяти и облака для режима копий, значок облака для
|
||||
режима переноса; для серверного альбома — значок облака.
|
||||
|
||||
#### Scenario: Локальный без синхронизации
|
||||
- **WHEN** показан локальный альбом без синхронизации
|
||||
- **THEN** вторая строка показывает значок карты памяти и число элементов
|
||||
|
||||
#### Scenario: Режим копий
|
||||
- **WHEN** локальный альбом синхронизируется в режиме копий
|
||||
- **THEN** вторая строка показывает значки карты памяти и облака и число элементов
|
||||
|
||||
#### Scenario: Режим переноса
|
||||
- **WHEN** локальный альбом синхронизируется в режиме переноса
|
||||
- **THEN** вторая строка показывает значок облака и число элементов
|
||||
|
||||
#### Scenario: Разные имена связанной пары
|
||||
- **WHEN** имя локального альбома не совпадает с именем связанного серверного
|
||||
- **THEN** первая строка показывает «имя → имя серверного»
|
||||
|
||||
#### Scenario: Одинаковые имена
|
||||
- **WHEN** имена совпадают
|
||||
- **THEN** стрелка и имя серверного не показываются
|
||||
|
||||
### Requirement: Панель атрибутов и действий альбома
|
||||
Приложение SHALL открывать панель «Атрибуты и действия» по свайпу справа налево на экране
|
||||
любого альбома — серверного и локального. Панель SHALL повторять структуру web: разделы
|
||||
«Обложка», «Информация», «Навигация» и раздел «Действия».
|
||||
|
||||
#### Scenario: Открытие свайпом
|
||||
- **WHEN** пользователь свайпает справа налево в открытом альбоме
|
||||
- **THEN** открывается панель «Атрибуты и действия»
|
||||
|
||||
#### Scenario: Структура панели
|
||||
- **WHEN** панель открыта
|
||||
- **THEN** показаны разделы «Обложка», «Информация», «Навигация» и «Действия»
|
||||
|
||||
#### Scenario: Закрытие панели
|
||||
- **WHEN** пользователь закрывает панель (свайп в обратную сторону или касание вне панели)
|
||||
- **THEN** панель скрывается, сетка медиа снова доступна
|
||||
|
||||
### Requirement: Источник атрибутов локального альбома
|
||||
Для локального альбома приложение SHALL показывать ту же панель «Атрибуты и действия», что и
|
||||
для серверного, а метаданные SHALL брать локально (имя, число элементов, локальная обложка,
|
||||
режим синхронизации, связанный серверный альбом). Действия локального альбома SHALL
|
||||
применяться локально.
|
||||
|
||||
#### Scenario: Локальные метаданные
|
||||
- **WHEN** пользователь открывает панель локального альбома
|
||||
- **THEN** раздел «Информация» показывает локальные имя, число элементов и обложку
|
||||
|
||||
#### Scenario: Неактивная навигация без связи
|
||||
- **WHEN** локальный альбом не связан с серверным
|
||||
- **THEN** строки «Участники» и «Ссылки для просмотра» показаны, но неактивны
|
||||
|
||||
#### Scenario: Активная навигация при связи
|
||||
- **WHEN** локальный альбом связан с серверным
|
||||
- **THEN** строки «Участники» и «Ссылки для просмотра» активны и относятся к связанному серверному альбому
|
||||
|
||||
### Requirement: Переименование и обложка локального альбома
|
||||
Для локального альбома действие «Переименовать» SHALL менять отображаемое имя в приложении, не
|
||||
изменяя медиа или bucket на устройстве, а «Выбрать обложку»/«Сбросить» SHALL назначать или
|
||||
снимать обложку из медиа этого альбома. Имя и обложка SHALL храниться локально.
|
||||
|
||||
#### Scenario: Переименование
|
||||
- **WHEN** пользователь вводит новое имя локального альбома
|
||||
- **THEN** панель, заголовок и карточка в списке альбомов показывают новое имя
|
||||
|
||||
#### Scenario: Назначение и сброс обложки
|
||||
- **WHEN** пользователь выбирает медиа обложкой или сбрасывает её
|
||||
- **THEN** панель и карточка альбома отражают выбор
|
||||
|
||||
### Requirement: Удаление локального альбома с телефона
|
||||
Действие «Удалить» локального альбома SHALL после подтверждения удалять все фото и видео
|
||||
этого альбома с устройства, запрашивая при необходимости системное согласие.
|
||||
|
||||
#### Scenario: Подтверждение
|
||||
- **WHEN** пользователь выбирает «Удалить» локального альбома
|
||||
- **THEN** открывается диалог с предупреждением, что медиа будут удалены с телефона
|
||||
|
||||
#### Scenario: Удаление
|
||||
- **WHEN** пользователь подтверждает удаление
|
||||
- **THEN** медиа удаляются с устройства, а альбом исчезает из приложения
|
||||
|
||||
### Requirement: Атрибуты альбома
|
||||
Панель SHALL показывать обложку с выбором и сбросом, информацию (название, описание, число
|
||||
фото и участников) и переходы к участникам, настройкам и ссылкам для просмотра. Управление
|
||||
обложкой и раздел «Навигация» SHALL быть доступны только владельцу и администратору.
|
||||
|
||||
#### Scenario: Управление обложкой
|
||||
- **WHEN** владелец или администратор выбирает или сбрасывает обложку
|
||||
- **THEN** изменение сохраняется на сервере и отражается в списке и панели
|
||||
|
||||
#### Scenario: Наблюдатель
|
||||
- **WHEN** наблюдатель открывает панель
|
||||
- **THEN** управление обложкой не показывается, а строки «Настройки» и «Ссылки для просмотра» неактивны
|
||||
|
||||
#### Scenario: Переход к настройкам
|
||||
- **WHEN** владелец или администратор открывает настройки из раздела «Навигация»
|
||||
- **THEN** доступны правка названия и описания и управление ссылками для просмотра
|
||||
|
||||
### Requirement: Действия с альбомом
|
||||
Панель SHALL содержать действия «Переименовать» и «Удалить». Удаление SHALL подтверждаться
|
||||
диалогом, а удалённый альбом SHALL помещать свои фото в корзину.
|
||||
|
||||
#### Scenario: Переименование
|
||||
- **WHEN** владелец или администратор выбирает «Переименовать» и вводит новое название
|
||||
- **THEN** название сохраняется на сервере и отражается в списке и заголовке
|
||||
|
||||
#### Scenario: Удаление с подтверждением
|
||||
- **WHEN** владелец выбирает «Удалить» и подтверждает в диалоге
|
||||
- **THEN** альбом удаляется, а его фото попадают в корзину
|
||||
|
||||
### Requirement: Лист атрибутов и действий фото
|
||||
Полноэкранный просмотр SHALL открывать лист «Атрибуты и действия» по свайпу вверх как в
|
||||
серверном, так и в device-просмотре. Лист SHALL повторять структуру web: разделы
|
||||
«Информация», «Метки» и «Действия». При зуме изображения, панораме или активном управлении
|
||||
видео свайп вверх SHALL NOT открывать лист.
|
||||
|
||||
#### Scenario: Открытие листа
|
||||
- **WHEN** пользователь свайпает вверх во вписанном изображении
|
||||
- **THEN** открывается лист «Атрибуты и действия» для текущего элемента
|
||||
|
||||
#### Scenario: Зум или панорама
|
||||
- **WHEN** изображение увеличено или открыта панорама, и пользователь свайпает вверх
|
||||
- **THEN** лист не открывается, жест обрабатывается изображением
|
||||
|
||||
#### Scenario: Видео
|
||||
- **WHEN** открыт видеоэлемент, и пользователь взаимодействует с плеером
|
||||
- **THEN** свайп вверх не мешает управлению воспроизведением
|
||||
|
||||
### Requirement: Атрибуты фото в листе
|
||||
Раздел «Информация» SHALL показывать автора, размер файла, тип, разрешение, EXIF-атрибуты,
|
||||
дату съёмки, координаты и дату загрузки. Раздел «Метки» SHALL позволять добавлять и снимать
|
||||
метки с поиском по меткам пользователя и созданием новых.
|
||||
|
||||
#### Scenario: Информация
|
||||
- **WHEN** лист открыт
|
||||
- **THEN** раздел «Информация» показывает метаданные и EXIF текущего фото
|
||||
|
||||
#### Scenario: Метки
|
||||
- **WHEN** пользователь добавляет или снимает метку в разделе «Метки»
|
||||
- **THEN** изменение сразу отражается в просмотре без перезагрузки
|
||||
|
||||
### Requirement: Настройки списка атрибутов
|
||||
Раздел «Информация» SHALL давать доступ к настройкам со списком EXIF-атрибутов, где
|
||||
пользователь отмечает отображаемые атрибуты и задаёт их подписи, с действиями «отметить все»
|
||||
и «снять все». Настройки SHALL применяться ко всем фото и сохраняться в профиле пользователя.
|
||||
|
||||
#### Scenario: Открытие настроек
|
||||
- **WHEN** пользователь открывает настройки атрибутов
|
||||
- **THEN** показан список EXIF-атрибутов с галочками видимости и полями подписей
|
||||
|
||||
#### Scenario: Сохранение настроек
|
||||
- **WHEN** пользователь отмечает атрибуты, задаёт подписи и сохраняет
|
||||
- **THEN** настройки сохраняются в профиле и применяются в разделе «Информация»
|
||||
|
||||
#### Scenario: Сброс настроек
|
||||
- **WHEN** пользователь сбрасывает настройки
|
||||
- **THEN** применяется базовый набор атрибутов с исходными подписями
|
||||
|
||||
### Requirement: Действия с фото в листе
|
||||
Раздел «Действия» SHALL содержать «Скачать», «Переместить», «Копировать», «Установить как
|
||||
обложку альбома» (по правам) и «Удалить»; из сетки также «Открыть (просмотр)». Удаление SHALL
|
||||
подтверждаться диалогом. Переименование SHALL выполняться по тапу на имя файла.
|
||||
|
||||
#### Scenario: Состав действий
|
||||
- **WHEN** лист открыт
|
||||
- **THEN** показаны действия «Скачать», «Переместить», «Копировать», «Установить как обложку» и «Удалить» по правам
|
||||
|
||||
#### Scenario: Удаление фото
|
||||
- **WHEN** пользователь выбирает «Удалить» и подтверждает
|
||||
- **THEN** фото уходит в корзину, а просмотр переходит к соседнему элементу
|
||||
|
||||
#### Scenario: Переименование фото
|
||||
- **WHEN** автор, владелец или администратор меняет имя файла на непустое
|
||||
- **THEN** новое имя сохраняется и отражается в просмотре и сетке
|
||||
|
||||
### Requirement: Источник атрибутов device-медиа
|
||||
Для device-медиа приложение SHALL показывать атрибуты и действия сопоставленного серверного
|
||||
фото, когда сопоставление есть, и локальные атрибуты и действия (имя, размер, тип,
|
||||
разрешение, дата, путь) при его отсутствии.
|
||||
|
||||
#### Scenario: Сопоставлено серверному фото
|
||||
- **WHEN** device-медиа сопоставлено серверному фото
|
||||
- **THEN** лист показывает серверные атрибуты и серверные действия этого фото
|
||||
|
||||
#### Scenario: Не сопоставлено
|
||||
- **WHEN** сопоставления с серверным фото нет
|
||||
- **THEN** лист показывает локальные атрибуты и локальные действия элемента
|
||||
|
||||
Reference in New Issue
Block a user