From bf3641f9e01d696959dcc52792c137d45371f899 Mon Sep 17 00:00:00 2001 From: Iu Date: Mon, 12 Oct 2026 12:15:13 +0300 Subject: [PATCH] chore(openspec): archive add-android-app and sync main specs --- .../.openspec.yaml | 0 .../2026-10-12-add-android-app}/design.md | 0 .../2026-10-12-add-android-app}/proposal.md | 0 .../specs/android-client/spec.md | 0 .../2026-10-12-add-android-app}/tasks.md | 0 openspec/specs/android-client/spec.md | 231 ++++++++++++++++++ 6 files changed, 231 insertions(+) rename openspec/changes/{add-android-app => archive/2026-10-12-add-android-app}/.openspec.yaml (100%) rename openspec/changes/{add-android-app => archive/2026-10-12-add-android-app}/design.md (100%) rename openspec/changes/{add-android-app => archive/2026-10-12-add-android-app}/proposal.md (100%) rename openspec/changes/{add-android-app => archive/2026-10-12-add-android-app}/specs/android-client/spec.md (100%) rename openspec/changes/{add-android-app => archive/2026-10-12-add-android-app}/tasks.md (100%) create mode 100644 openspec/specs/android-client/spec.md diff --git a/openspec/changes/add-android-app/.openspec.yaml b/openspec/changes/archive/2026-10-12-add-android-app/.openspec.yaml similarity index 100% rename from openspec/changes/add-android-app/.openspec.yaml rename to openspec/changes/archive/2026-10-12-add-android-app/.openspec.yaml diff --git a/openspec/changes/add-android-app/design.md b/openspec/changes/archive/2026-10-12-add-android-app/design.md similarity index 100% rename from openspec/changes/add-android-app/design.md rename to openspec/changes/archive/2026-10-12-add-android-app/design.md diff --git a/openspec/changes/add-android-app/proposal.md b/openspec/changes/archive/2026-10-12-add-android-app/proposal.md similarity index 100% rename from openspec/changes/add-android-app/proposal.md rename to openspec/changes/archive/2026-10-12-add-android-app/proposal.md diff --git a/openspec/changes/add-android-app/specs/android-client/spec.md b/openspec/changes/archive/2026-10-12-add-android-app/specs/android-client/spec.md similarity index 100% rename from openspec/changes/add-android-app/specs/android-client/spec.md rename to openspec/changes/archive/2026-10-12-add-android-app/specs/android-client/spec.md diff --git a/openspec/changes/add-android-app/tasks.md b/openspec/changes/archive/2026-10-12-add-android-app/tasks.md similarity index 100% rename from openspec/changes/add-android-app/tasks.md rename to openspec/changes/archive/2026-10-12-add-android-app/tasks.md diff --git a/openspec/specs/android-client/spec.md b/openspec/specs/android-client/spec.md new file mode 100644 index 0000000..31cf436 --- /dev/null +++ b/openspec/specs/android-client/spec.md @@ -0,0 +1,231 @@ +# 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, а при отсутствии сети — из локального кэша. + +#### 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** приложение показывает сообщение, при необходимости адаптированное под язык интерфейса