Files
minstrel/flutter_client
bvandeusen 84f16c25f6 feat(ui): Lucide migration unit 1 — dependency + heart (#60)
Design system mandates Lucide, not Material. Foundation before the
mechanical Icons.* sweep:

- pubspec: add flutter_lucide ^1.11.0
- shared/widgets/lucide_heart.dart: LucideHeart renders the verified
  lucide-icons/lucide heart path as outline (stroke) or filled, via
  flutter_svg, tinted by color — Lucide ships no filled heart, so the
  liked state fills the same Lucide silhouette (user-chosen approach)
- like_button: use LucideHeart instead of Icons.favorite/_border
- notification drawables re-derived from the verbatim Lucide heart
  path (border = stroke, filled = fill); separators spaced for
  Android pathData

Unit 2 (mechanical Icons.* -> LucideIcons.* sweep) follows once this
is CI-green. pubspec.lock regenerated by CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:18:22 -04:00
..

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

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

.forgejo/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 Forgejo 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 Forgejo 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.