docs: add product idea, design prototypes and development documentation

- IDEA.md: product vision from the user's perspective (input for design)
- Architecture, ADRs, data model, REST API overview
- Auth and roles, library storage (NFS, path regexes, scanner, search),
  playback, recommendation system ("My Wave"), track upload queue
- Web, Android (offline, self-update), i18n, design system
- Testing strategy, Gitea Actions CI/CD, deployment, conventions, roadmap
- Clickable web/Android design prototypes and wave button variants
- .claude/launch.json to serve the prototypes locally

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-19 23:21:31 +03:00
1 parent 36e99fe960
commit 9eb1c25147
30 files changed
+7538

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
# Android-приложение (`apps/mobile`)
## Стек
| Задача | Выбор |
|---|---|
| Фреймворк | React Native (New Architecture) + Expo SDK, **prebuild / dev client** (нативный код под контролем) |
| Навигация | `expo-router` (файловые маршруты) |
| Аудио | `react-native-track-player` v4 — ExoPlayer, MediaSession, уведомление, экран блокировки, Android Auto-совместимость |
| Анимация волны | `@shopify/react-native-skia` (порт `wave.js`, общие формулы в `packages/core/wave-anim`) + `react-native-reanimated` |
| Серверное состояние | TanStack Query (+ persist в MMKV) |
| Клиентское состояние | Zustand (общие сторы из `packages/core`) |
| Хранилище | `react-native-mmkv` (настройки, кэш), `expo-sqlite` (офлайн-каталог, очередь событий), `expo-secure-store` (refresh-токен) |
| Загрузки | `expo-file-system` (фоновые загрузки, resumable) |
| Сеть | `@react-native-community/netinfo` (Wi-Fi / сотовая → качество) |
| i18n | i18next, словари из `packages/i18n` |
| Минимальная версия | Android 8.0 (API 26), target — актуальный API |
## Экраны
Макет — `Pipu Android.dc.html`, сводка решений — [design.md](design.md#android).
- **Подключение и вход**: адрес сервера (проверка `GET /server-info`: версия сервера, HTTPS, поддерживается ли эта версия
приложения), список сохранённых серверов, затем логин, пароль, язык. Далее те же setup-экраны, что и в вебе.
- **Нижняя навигация**: Главная · Поиск · Коллекция · Профиль. Мини-плеер над навигацией; свайп вверх — полноэкранный
плеер в одном из двух видов (выбор в профиле): `cover` — крупная обложка (по умолчанию) или `wave` — визуализация волны;
прогресс, лайк / дизлайк, скачать, очередь (нижний лист).
- **Поиск**: недавние запросы (хранятся локально), подсказка про опечатки и раскладку, значок «скачано» у треков.
- **Главная**: кнопка «Моя волна» (`orb` или `liquid` по настройке, тот же алгоритм, что в вебе, на Skia; пульс в такт BPM / energy из `features` трека — на Android
спектр недоступен из ExoPlayer без нативного модуля, поэтому анимация управляется метаданными и позицией трека),
выбор занятия / настроения.
- **Коллекция**: История, Лайки, Дизлайки, **Скачанное**.
- **Коллекция → Скачанное**: занято на устройстве / свободно в телефоне, «Скачивать новые лайки», «Скачать все лайки»,
очередь загрузок с пометкой «по Wi-Fi».
- **Профиль**: смена пароля (лист), язык, тема, вид волны, вид плеера, качество звука (лист: Wi-Fi / сотовая / загрузки),
«только Wi-Fi для загрузок», кэш недавних треков (размер + «Очистить»), проверка обновлений (точка, если есть новая версия),
текущий сервер, «Админка — откроется в браузере» (для admin / moderator), выход.
- **Офлайн-баннер** вверху, когда нет связи с сервером.
- Админку в мобильном **не делаем** на первом этапе (ссылка «открыть в браузере»).
## Воспроизведение
- `packages/core` содержит общую логику очереди, событий и сохранения позиции; мобильный реализует адаптер
`AudioEngine` поверх `react-native-track-player`.
- Фоновый сервис плеера (`PlaybackService`) обрабатывает кнопки уведомления / гарнитуры и шлёт события,
даже когда UI выгружен.
- Если трек скачан — играется локальный файл, иначе стрим с качеством по типу сети.
- Встроенный кэш ExoPlayer (`maxCacheSize` 500 МБ) для недавних стримов.
- Аудиофокус: пауза при звонке, приглушение при навигационных подсказках.
## Офлайн
- Пользователь скачивает трек, альбом, «все лайки» (опционально — авто-загрузка новых лайков).
- Файлы — во внутреннем хранилище приложения (`documentDirectory/offline/<trackId>.opus`), не видны другим приложениям.
- SQLite хранит метаданные скачанных треков (название, исполнитель, обложка-файл, длительность, features) —
раздел «Скачанное» и плеер работают полностью без сети.
- Очередь загрузок: не более 2 параллельно, возобновление после обрыва, «только по Wi-Fi» по умолчанию.
- Без сети:
- доступны «Скачанное» и офлайн-волна (локальный упрощённый алгоритм: перемешивание скачанных с учётом лайков
и энергии под выбранный контекст);
- события пишутся в SQLite-очередь и отправляются при появлении сети (`POST /events`, идемпотентно);
- реакции (лайк / дизлайк) — оптимистично локально + очередь мутаций.
- Подписанные ссылки не кэшируются — при восстановлении сети запрашиваются заново.
## Самообновление
Приложение распространяется только через Gitea Releases (не Google Play).
**Поток:**
1. При запуске (не чаще раза в 6 ч) и вручную в профиле: `GET /api/v1/app/android/latest` на **сервере Pipu**.
Сервер сам опрашивает Gitea API (`GET /api/v1/repos/{owner}/{repo}/releases/latest`) с серверным токеном,
кэширует ответ на 10 мин и APK на диске. Так:
- мобильному приложению не нужен доступ к Gitea и токен (репозиторий может быть приватным);
- версия приложения согласована с версией сервера (`min_supported` из `server-info`).
2. Если `version_code` новее — диалог с changelog (Markdown из релиза), кнопки «Обновить» / «Позже».
Если текущая версия ниже `min_supported` — обновление обязательно.
3. Загрузка APK через `expo-file-system` в `cacheDirectory` с прогрессом; проверка **SHA-256** (значение из релиза).
4. Установка: `Intent.ACTION_VIEW` с `content://` URI через `FileProvider` и флагом `FLAG_GRANT_READ_URI_PERMISSION`
(небольшой локальный Expo-модуль `modules/apk-installer` на Kotlin; при необходимости — `PackageInstaller` session API).
5. Если нет разрешения «установка из неизвестных источников» — открываем `Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES`
для нашего пакета с пояснением.
**Требования:**
- В манифесте: `REQUEST_INSTALL_PACKAGES`, `FileProvider`.
- Все релизы подписываются **одним и тем же ключом** (keystore в секретах CI) — иначе Android откажет в обновлении.
- `versionCode` монотонно растёт: `major·10000 + minor·100 + patch` (см. [ci-cd.md](ci-cd.md)).
- Ассеты релиза: `pipu-music-<version>.apk`, `pipu-music-<version>.apk.sha256`.
## Безопасность
- Refresh-токен — `expo-secure-store` (Android Keystore).
- HTTPS обязателен; для self-signed сертификатов — возможность добавить пользовательский CA через
`network_security_config` (собирается в CI по флагу) или пиннинг отпечатка, введённого при подключении к серверу.
- `android:allowBackup="false"` для данных с токенами.