# Spec Delta ## Purpose Android-клиент для self-hosted galo: настройка подключения к серверу, авторизация, просмотр альбомов и медиа, а также фоновая загрузка фотографий и видео с телефона с офлайн-кэшем. ## ADDED 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, а при отсутствии сети — из локального кэша. #### 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** приложение показывает сообщение, при необходимости адаптированное под язык интерфейса