# android-client Specification ## Purpose Android-клиент для self-hosted galo: настройка подключения к серверу, авторизация, просмотр альбомов и медиа, а также фоновая загрузка фотографий и видео с телефона с офлайн-кэшем. ## Requirements ### Requirement: Настройка подключения к серверу Приложение SHALL требовать явного указания базового URL инстанса galo при первом запуске и SHALL проверять доступность сервера перед переходом к работе. #### Scenario: Успешная настройка - **WHEN** пользователь вводит достижимый базовый URL и подтверждает - **THEN** приложение проверяет `GET /api/health` и при успехе переходит к входу #### Scenario: Сервер недоступен - **WHEN** сервер недоступен или health вернул ошибку - **THEN** приложение показывает ошибку и остаётся на экране настройки, сохраняя введённый URL ### Requirement: Небезопасное соединение по LAN Приложение SHALL по умолчанию запрещать незашифрованный HTTP и SHALL разрешать его только после явного включения пользователем режима небезопасного соединения. #### Scenario: HTTP без разрешения - **WHEN** пользователь указывает `http`-URL и режим небезопасного соединения выключен - **THEN** приложение блокирует подключение и предлагает включить режим #### Scenario: Режим включён - **WHEN** пользователь включил режим небезопасного соединения - **THEN** HTTP-подключение разрешено, а интерфейс явно отмечает небезопасное соединение ### Requirement: Разрешение относительных URL объектов Приложение SHALL трактовать относительные URL объектов из REST-ответов и SSE как пути на выбранном базовом URL сервера. #### Scenario: Отдача объекта - **WHEN** API возвращает `thumb_url` вида `/api/objects/?...` - **THEN** приложение запрашивает объект с того же инстанса, что и базовый URL ### Requirement: Вход и хранение сессии Приложение SHALL позволять вход и регистрацию по email и паролю и SHALL хранить токены только в защищённом хранилище платформы. #### Scenario: Успешный вход - **WHEN** вход проходит успешно - **THEN** токены сохраняются в защищённом хранилище, пользователь видит список альбомов #### Scenario: Перезапуск с сессией - **WHEN** приложение перезапущено с сохранённой сессией - **THEN** пользователь остаётся авторизованным без повторного ввода пароля ### Requirement: Автоматическое обновление токена Приложение SHALL автоматически обновлять access-токен при ответе 401, пока refresh-токен действителен, и SHALL завершать сессию при неудачном обновлении. #### Scenario: Прозрачное обновление - **WHEN** запрос получает 401 и refresh-токен действителен - **THEN** приложение обновляет токен и повторяет исходный запрос без участия пользователя #### Scenario: Обновление не удалось - **WHEN** обновление токена завершилось ошибкой - **THEN** сессия очищается и пользователь возвращается на экран входа ### Requirement: Список альбомов Приложение SHALL показывать доступные пользователю альбомы одним смешанным списком: серверные альбомы, получаемые из API, а при отсутствии сети — из локального кэша, и альбомы устройства. Элементы SHALL различать источник. Связанная пара «альбом устройства → серверный альбом» SHALL отображаться одной карточкой альбома устройства с указателем связи и меткой режима; связанный серверный альбом SHALL NOT показываться в списке отдельной записью. #### Scenario: Онлайн - **WHEN** авторизованный пользователь открывает приложение - **THEN** отображается список альбомов с обложками или плейсхолдером #### Scenario: Офлайн из кэша - **WHEN** сеть недоступна, но кэш существует - **THEN** список альбомов отображается из кэша с признаком офлайн-режима #### Scenario: Альбомы устройства - **WHEN** у приложения есть доступ к медиа устройства - **THEN** в списке появляются альбомы устройства наряду с серверными и помечены источником #### Scenario: Связанная пара - **WHEN** альбом устройства синхронизируется с серверным альбомом - **THEN** в списке показывается одна карточка альбома устройства с указателем связи и меткой режима, а связанный серверный альбом отдельной записью не показывается ### Requirement: Экран альбома устройства и настройки синхронизации Приложение SHALL открывать альбом устройства с сеткой медиа и SHALL предоставлять настройки синхронизации этого альбома по свайпу вправо. Настройки SHALL содержать только выбор режима синхронизации. #### Scenario: Открытие альбома устройства - **WHEN** пользователь выбирает альбом устройства в списке - **THEN** открывается сетка медиа этого альбома #### Scenario: Настройки по свайпу - **WHEN** пользователь свайпает вправо в открытом альбоме устройства - **THEN** открываются настройки синхронизации с выбором режима и только им #### Scenario: Метка перенесённого элемента - **WHEN** оригинал элемента разгружен на сервер в режиме переноса - **THEN** в сетке на элементе показывается значок облака #### Scenario: Метка потерянного элемента - **WHEN** серверной копии разгруженного элемента больше нет в целевом альбоме - **THEN** в сетке на элементе показывается перечёркнутый значок облака ### Requirement: Доступ к альбомам устройства Приложение SHALL запрашивать доступ к медиа устройства, нужный для перечисления альбомов и чтения файлов, и SHALL работать без него, не показывая альбомы устройства. #### Scenario: Доступ предоставлен - **WHEN** пользователь предоставляет доступ к медиа устройства - **THEN** приложение перечисляет альбомы устройства и их содержимое #### Scenario: Доступ не предоставлен - **WHEN** доступ к медиа устройства не предоставлен - **THEN** приложение продолжает работать с серверными альбомами и не показывает альбомы устройства ### Requirement: Постраничный просмотр альбома Приложение SHALL показывать сетку медиа альбома с постраничной загрузкой по курсору и SHALL подгружать следующую страницу при достижении конца списка. #### Scenario: Догрузка страницы - **WHEN** пользователь прокручивает сетку до конца - **THEN** приложение запрашивает следующую страницу по курсору и добавляет её к сетке #### Scenario: Пустой альбом - **WHEN** в альбоме нет медиа - **THEN** отображается пустое состояние ### Requirement: Просмотр медиа на весь экран Полноэкранный просмотр SHALL поддерживать изображение с зумом пальцами, видео с постером и управлением воспроизведением, а также панораму 360; навигация между элементами альбома SHALL выполняться свайпом. #### Scenario: Свайп между элементами - **WHEN** пользователь свайпает в просмотре - **THEN** показывается следующий или предыдущий элемент альбома без выхода из просмотра #### Scenario: Видео - **WHEN** открыт видеоэлемент - **THEN** показывается постер и доступно воспроизведение с перемоткой ### Requirement: Обновления в реальном времени Пока экран альбома активен, приложение SHALL отображать изменения по SSE (новые, обновлённые и удалённые медиа, социальные события) и SHALL догонять пропущенное при переподключении. #### Scenario: Новое фото с другого устройства - **WHEN** другое устройство добавляет фото в открытый альбом - **THEN** новое фото появляется в сетке без ручного обновления #### Scenario: Переподключение - **WHEN** SSE-соединение разорвалось и восстановилось - **THEN** приложение дозагружает изменения, произошедшие за время разрыва ### Requirement: Выбор медиа и альбома для загрузки Приложение SHALL предоставлять выбор одного или нескольких фото и видео из системной галереи и SHALL требовать выбора альбома назначения перед постановкой в очередь. #### Scenario: Постановка в очередь - **WHEN** пользователь выбрал файлы и альбом назначения - **THEN** задания загрузки ставятся в очередь и пользователь видит их статус #### Scenario: Отказ сервера - **WHEN** сервер отклоняет загрузку (например, из-за прав или размера) - **THEN** задание помечается ошибкой с причиной от сервера ### Requirement: Фоновая очередь загрузки Загрузка SHALL выполняться по одному файлу за HTTP-запрос и SHALL продолжаться при уходе приложения из foreground, показывая прогресс в уведомлении; при ошибке SHALL выполняться повтор с нарастающей задержкой. #### Scenario: Уход в фон - **WHEN** приложение уходит в фон во время загрузки - **THEN** загрузка продолжается и прогресс отображается в уведомлении #### Scenario: Повтор при ошибке - **WHEN** загрузка файла завершилась сетевой ошибкой - **THEN** задание повторяется, а после исчерпания попыток помечается ошибкой ### Requirement: Условия сети для загрузки Приложение SHALL позволять ограничить загрузку сетью Wi-Fi; при ожидании подходящей сети задание остаётся в очереди и не выполняется. #### Scenario: Только Wi-Fi - **WHEN** ограничение «только Wi-Fi» включено, а доступна только сотовая сеть - **THEN** загрузка не начинается до появления Wi-Fi, а задание остаётся в очереди ### Requirement: Сохранность очереди между запусками Очередь загрузки SHALL сохраняться на устройстве и SHALL продолжаться после перезапуска приложения или устройства. #### Scenario: Возобновление после перезапуска - **WHEN** приложение или устройство перезапускается при незавершённой очереди - **THEN** незавершённые задания возобновляются ### Requirement: Конвертация HEIC в JPEG Файлы HEIC/HEIF SHALL конвертироваться на устройстве в JPEG перед загрузкой с сохранением даты съёмки и геолокации. #### Scenario: Загрузка HEIC - **WHEN** пользователь ставит в очередь HEIC-файл - **THEN** загружается JPEG-версия, а дата съёмки и координаты из EXIF сохраняются #### Scenario: Ошибка конвертации - **WHEN** конвертация завершилась ошибкой - **THEN** задание помечается ошибкой, повреждённый файл не загружается ### Requirement: Кэш медиа по устойчивому ключу Приложение SHALL кэшировать полученные превью и оригиналы по устойчивому идентификатору объекта, не зависящему от подписи и срока действия URL. #### Scenario: Смена подписи - **WHEN** URL одного и того же объекта со временем получает новую подпись - **THEN** приложение переиспользует закэшированный объект вместо повторной загрузки #### Scenario: Офлайн-отдача из кэша - **WHEN** сеть недоступна, а объект есть в кэше - **THEN** объект отображается из кэша ### Requirement: Единый формат ошибок API Приложение SHALL распознавать ошибки API формата `{error:{code,message}}` и SHALL показывать пользователю понятное сообщение. #### Scenario: Ошибка с кодом и сообщением - **WHEN** сервер возвращает ошибку с `code` и `message` - **THEN** приложение показывает сообщение, при необходимости адаптированное под язык интерфейса ### Requirement: Зум изображения жестами Полноэкранный просмотр изображения SHALL поддерживать увеличение и уменьшение щипком (pinch), а при увеличенном изображении — панорамирование перетаскиванием. #### Scenario: Щипок увеличивает изображение - **WHEN** пользователь разводит пальцы на изображении - **THEN** изображение увеличивается относительно вписанного вида #### Scenario: Панорамирование при увеличении - **WHEN** изображение увеличено, и пользователь перетаскивает его пальцем - **THEN** видимая область изображения смещается, а границы изображения не отрываются от краёв области просмотра ### Requirement: Двойной тап переключает вписанный вид и 100% Двойной тап по изображению SHALL переключать вписанный вид и масштаб 100% натурального размера с якорем в точку нажатия. #### Scenario: Вход в 100% из вписанного вида - **WHEN** изображение показано вписанным, и пользователь делает двойной тап - **THEN** изображение показывается в масштабе 100% натурального размера с центром в точке нажатия #### Scenario: Возврат во вписанный вид - **WHEN** изображение увеличено, и пользователь делает двойной тап - **THEN** изображение возвращается во вписанный вид ### Requirement: Предел зума по натуральному размеру Зум изображения SHALL ограничиваться снизу вписанным видом, а сверху — 200% натурального размера изображения; при входе в зум SHALL догружаться оригинал изображения. #### Scenario: Верхний предел - **WHEN** пользователь продолжает увеличивать изображение за 200% натурального размера - **THEN** масштаб останавливается на 200%, изображение не увеличивается дальше #### Scenario: Оригинал при увеличении - **WHEN** пользователь входит в зум - **THEN** догружается оригинал изображения и подменяет превью по факту загрузки ### Requirement: Навигация и сброс при зуме Пока изображение увеличено, горизонтальный свайп SHALL NOT листать альбом, а жест 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 довосстанавливаться при следующей проверке (при запуске и при открытии альбома). #### 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** лист показывает локальные атрибуты и локальные действия элемента ### Requirement: Живое обновление списка альбомов Пока пользователь авторизован, приложение SHALL поддерживать персональный поток и применять события жизненного цикла альбома к локальному кэшу: создавать или заменять альбом по полученному объекту и удалять его по идентификатору, не выполняя дополнительный запрос списка. #### Scenario: Альбом создан или изменён в другом месте - **WHEN** в персональный поток приходит событие создания или изменения доступного альбома - **THEN** альбом появляется в списке или заменяется без запроса списка #### Scenario: Неизвестное событие - **WHEN** в персональный поток приходит событие, не относящееся к жизненному циклу альбома - **THEN** приложение не меняет локальный список #### Scenario: Навигация между экранами - **WHEN** пользователь переходит с главного экрана на другой экран приложения - **THEN** подписка на персональный поток не пересоздаётся #### Scenario: Выход из аккаунта - **WHEN** пользователь выходит из аккаунта - **THEN** подписка на персональный поток закрывается ### Requirement: Догон списка при переподключении персонального потока После восстановления персонального потока приложение SHALL приводить локальный список альбомов в соответствие с серверным снимком, чтобы отразить изменения, пропущенные за время разрыва соединения. #### Scenario: Переподключение - **WHEN** персональный поток восстановился после разрыва - **THEN** приложение перезапрашивает список альбомов, удаляя из кэша альбомы, которых на сервере больше нет ### Requirement: Потеря доступа к альбому Приложение SHALL переставать отображать альбом, удалённый владельцем или ставший недоступным после исключения участника: альбом SHALL исчезать из локального кэша и списка, а экран открытого альбома SHALL возвращаться к списку. #### Scenario: Альбом удалён во время просмотра - **WHEN** владелец удаляет альбом, пока участник просматривает экран этого альбома - **THEN** экран перестаёт показывать альбом и возвращается к списку #### Scenario: Участник исключён - **WHEN** участника исключают из альбома - **THEN** альбом исчезает из его списка, а открытый экран возвращается к списку #### Scenario: Экран альбома не открыт - **WHEN** альбом удалён, а его экран не открыт - **THEN** карточка альбома исчезает из списка #### Scenario: Отсутствие альбома в кэше без сети - **WHEN** приложение открывает экран альбома, которого нет в кэше, и сеть недоступна - **THEN** экран не считается потерявшим доступ и не закрывается сам ### Requirement: Выбор сортировки списка альбомов Приложение SHALL предоставлять выбор порядка карточек в списке альбомов: «по времени изменения», «по названию» или «по числу фото», а также обратный порядок для выбранного варианта. Выбор SHALL сохраняться локально на устройстве и применяться при следующем запуске. Порядок SHALL применяться ко всему списку: карточки альбомов устройства и серверных альбомов упорядочиваются вместе. #### Scenario: Выбор варианта - **WHEN** пользователь выбирает вариант сортировки - **THEN** карточки списка перестраиваются в соответствии с выбором #### Scenario: Обратный порядок - **WHEN** пользователь включает обратный порядок - **THEN** выбранный вариант применяется в обратном направлении #### Scenario: Альбомы устройства - **WHEN** выбрана любая сортировка - **THEN** карточки альбомов устройства упорядочиваются вместе с серверными, а для варианта «по времени изменения» используется дата самого свежего элемента альбома устройства #### Scenario: Сортировка по умолчанию - **WHEN** пользователь ещё не выбирал сортировку - **THEN** карточки упорядочены по времени изменения, самые свежие сверху, обратный порядок выключен #### Scenario: Сохранение выбора - **WHEN** пользователь перезапускает приложение - **THEN** список открывается с сохранёнными вариантом и направлением ### Requirement: Действия потерявшего серверную копию превью Шит потерявшего серверную копию элемента SHALL предлагать действие «Удалить с устройства» и SHALL NOT показывать серверные атрибуты и действия. #### Scenario: Только удаление с устройства - **WHEN** пользователь открывает атрибуты потерявшего серверную копию элемента - **THEN** доступно действие «Удалить с устройства», а серверные действия отсутствуют