Files
minstrel/flutter_client/README.md
T
bvandeusen 3cf829752b
flutter / analyze-test-build (push) Failing after 2s
test-go / test (push) Successful in 33s
android / Build + lint + test (push) Failing after 41s
test-web / test (push) Successful in 41s
android / Build signed release APK (push) Has been skipped
test-go / integration (push) Successful in 11m16s
chore(ci): migrate workflows to gitea + consolidate keystore secret
- rename .forgejo/workflows/ to .gitea/workflows/ (git mv preserves
  history); Gitea Actions reads either path, but the directory now
  matches the active platform
- collapse ANDROID_STORE_PASSWORD + ANDROID_KEY_PASSWORD into a
  single ANDROID_KEYSTORE_PASSWORD secret (PKCS12 keystores require
  the two passwords to be identical, so the duplication carried no
  information); build.gradle.kts still reads two env vars to stay
  format-agnostic, both now sourced from the same secret in CI
- ignore android/*.keystore, android/*.keystore.*, android/*.jks,
  android/keystore.properties so the regenerated signing material
  never reaches a remote on accident
- update prose references from Forgejo to Gitea in CLAUDE.md and
  the docs; the historical "migrated from Forgejo" note in CLAUDE.md
  is kept intentionally; forgejo/upload-artifact@v3 action refs are
  left untouched (canonical artifact action, resolves cleanly under
  Gitea Actions)

Operator side: ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_PASSWORD, and
ANDROID_KEYSTORE_B64 secrets registered in Gitea before this lands.
2026-05-31 23:05:03 -04:00

82 lines
3.3 KiB
Markdown

# Minstrel mobile client
Flutter (iOS + Android) sibling of the SvelteKit web SPA. v1 first
slice ships: scaffold, auth, library browse (home / artist / album),
likes, player with background audio + lock-screen controls.
## Setup
```bash
cd flutter_client
flutter pub get
./tool/sync_shared.sh # copy tokens / error-copy / placeholders from ../web
dart run tool/gen_tokens.dart # regen lib/theme/tokens.dart from shared/fabledsword.tokens.json
flutter run
```
`sync_shared.sh` is idempotent. CI runs it on every build; rerun locally
whenever `web/src/lib/styles/tokens.json`,
`web/src/lib/styles/error-copy.json`, or
`web/static/placeholders/album-fallback.svg` changes.
## Project layout
See `docs/superpowers/specs/2026-05-02-flutter-mobile-foundation-design.md`
section 3. Briefly:
- `lib/api/` — dio client, ApiError, error-copy loader, per-surface endpoints.
- `lib/auth/` — server URL screen, login screen, AuthController + secure storage.
- `lib/library/` — home, artist detail, album detail, providers, widgets.
- `lib/likes/` — LikeButton + optimistic toggle controller.
- `lib/player/` — audio handler, player provider, mini PlayerBar, NowPlaying.
- `lib/theme/` — FabledSword token Dart class (generated), ThemeExtension, ThemeData.
- `lib/shared/` — routing (go_router shell), version gate, connection error banner.
`shared/fabledsword.tokens.json` and `assets/error-copy.json` are
synced from web/. Don't edit them directly — edit `web/src/lib/styles/`
and re-run `./tool/sync_shared.sh`.
## Architecture
- **State:** Riverpod 2 (`AsyncValue`, `AsyncNotifier`, family providers).
- **HTTP:** dio 5 with a Bearer-auth interceptor; 401 clears the session
and the router redirects to login.
- **Audio:** just_audio wrapped by audio_service for background
playback + lock-screen / notification controls.
- **Auth:** Bearer tokens in flutter_secure_storage. Server already
supports both cookie (web SPA) and Bearer (`internal/auth/session.go`).
- **Routing:** go_router with a ShellRoute so the PlayerBar persists
across navigation. Cold launch flow: no server-url → `/server-url`,
url set / no token → `/login`, token present → `/home`.
## CI
`.gitea/workflows/flutter.yml` runs on push to dev/main, tag pushes,
PR to main, and manual dispatch — path-filtered to `flutter_client/**`
plus the shared web inputs. Steps: sync, analyze, test, build APK.
Debug APKs upload as artifacts; release APKs attach to the Gitea
release on tag.
## Versioning
`pubspec.yaml` `version` mirrors the server tag (e.g. server `v0.1.0`
→ Flutter `0.1.0+1`). The server's `/healthz` returns
`min_client_version`; old clients show an Update Required modal and
refuse to operate. Bump `internal/server/version.go` `MinClientVersion`
when a server-side change requires a paired client update.
## Distribution
- **Android:** APK on the Gitea release page. No Play Store for v1.
- **iOS:** TestFlight on tag (manual upload v1, automate later).
## Out-of-scope for slice 1
Search, discover, requests, settings beyond server URL, admin,
listening history, playlists, offline cache (#357). Those land in
subsequent slices with their own brainstorm/spec/plan cycles. Per the
operator's M7 ordering decision (2026-05-02), slice 2+ pauses to ship
the missing web features first (#352 playlists, #362 theme toggle,
#363 queue UI, #364 listening history, etc.) before mirroring on
Flutter.