Commit Graph

16 Commits

Author SHA1 Message Date
bvandeusen f675782bf5 feat(flutter/player): NowPlaying full-screen view with scrubber
Reached by tapping the PlayerBar. Reads media + playback streams; seek
dispatches through the audio handler. Swipe-down dismiss is a stand-in
chevron-down AppBar leading icon for v1.
2026-05-02 17:40:13 -04:00
bvandeusen bbfeb5d3ff feat(flutter/player): mini PlayerBar in shell + tap-to-play wiring
PlayerBar reads playbackState + mediaItem streams; hidden when no
queue. Home track-row tap, album play button, album track tap, artist
play button (shuffle) all route to playerActions.playTracks. Token +
baseUrl forwarded to the audio handler before each new queue.
2026-05-02 17:39:09 -04:00
bvandeusen 80f98b3243 feat(flutter/player): MinstrelAudioHandler + just_audio + audio_service
audio_service runs the handler in a background isolate; UI watches
playbackState/mediaItem/queue streams through Riverpod. AndroidManifest
gets the foreground-service media-playback permission; Info.plist gets
UIBackgroundModes=audio. Bearer token attached to stream URLs via
just_audio's per-source headers.
2026-05-02 17:37:14 -04:00
bvandeusen 44e2623b3c feat(flutter/likes): LikeButton with optimistic toggle + rollback
LikedIdsController loads /api/likes/ids once and mutates the local set
on toggle. Failed mutations roll the set back so the icon never lies.
Wired into ArtistDetail header, AlbumDetail header, and per-track in
the album track list.
2026-05-02 17:34:59 -04:00
bvandeusen 6ec7cea362 feat(flutter/library): artist + album detail screens
Headers carry placeholder play buttons (wired in Task 18) + room for
LikeButton (Task 16). Artist screen has a 2-column album grid; album
screen has a track list with mm:ss durations. Routes :id-parameterized
under the shell so the PlayerBar persists.
2026-05-02 17:32:34 -04:00
bvandeusen 5ef782905e feat(flutter/home): home screen with five sections
Sections mirror the web home: Recently added · Rediscover (albums +
artists) · Most played · Last played artists. Tap routes pushed for
albums/artists; track-tap is wired in the player task. Pull-to-refresh
re-fetches /api/home.
2026-05-02 17:30:42 -04:00
bvandeusen c47deb3c87 feat(flutter/library): card + row widgets (artist/album/track)
HorizontalScrollRow takes an optional shared ScrollController so
multi-row sections (Most played: 3 rows) couple their scroll like the
web HorizontalScrollRow.svelte. Cards fall back to the synced
album-fallback.svg when coverUrl is empty.
2026-05-02 17:29:03 -04:00
bvandeusen 3e267918b7 feat(flutter/library): API endpoints + Riverpod providers
LibraryApi wraps GET /api/home, /api/artists/{id}(/tracks),
/api/albums/{id}. dioProvider builds an authenticated dio (token
resolver reads session_token from secure storage on every request).
homeProvider, artistProvider(id), albumProvider(id) sit on top.
2026-05-02 17:26:57 -04:00
bvandeusen d115c267f4 feat(flutter): GoRouter shell + version gate + auth-aware redirects
Cold launch flow: no server-url -> /server-url. URL set, no token ->
/login. Token present -> /home with shell. VersionGate runs once when
the URL changes, hits /healthz, blocks if min_client_version > package
version. /home is a placeholder until Task 14.

Replaces Navigator.pushReplacementNamed in server_url_screen +
login_screen with context.go now that go_router owns the routes.
2026-05-02 17:24:08 -04:00
bvandeusen 9cbbfcff6c feat(flutter/auth): login screen + AuthApi.login posting to /api/auth/login
Login uses a non-authenticated dio (token resolver returns null) since
we don't have one yet. On success, setSession persists token + user
into secure storage and the AuthController state flips, which the
router watches to navigate.
2026-05-02 17:21:42 -04:00
bvandeusen 3cf874ab69 feat(flutter/auth): server URL screen + auth provider + secure storage
AuthController is an AsyncNotifier holding the currently-logged-in
User. server_url, session_token, current_user all live in
flutter_secure_storage. ServerUrlScreen probes /healthz before saving
the URL so we don't store a bogus base.
2026-05-02 17:20:26 -04:00
bvandeusen c0785cf894 feat(flutter/models): User, ArtistRef, AlbumRef, TrackRef, HomeData
Field names mirror web/src/lib/api/types.ts and the server JSON shape
(internal/api/types.go's HomePayload). Hand-written DTOs per spec
section 10 -- codegen revisits at ~30 models.

Adopts server contract over plan-body mock:
- cover_url (not cover_art_url) for artist/album refs
- duration_sec (not duration_ms) for albums and tracks
- stream_url, disc_number on TrackRef
- sort_name/album_count on ArtistRef; sort_title/year/track_count on AlbumRef

HomeData section keys match server: recently_added_albums,
rediscover_albums, rediscover_artists, most_played_tracks,
last_played_artists.
2026-05-02 17:18:12 -04:00
bvandeusen c93b852e65 feat(flutter/api): dio client with Bearer interceptor + ApiError
ApiError.fromDio handles both server envelope shapes: {"error":"code"}
(admin endpoints) and {"error":{"code","message"}} (most others). Same
dual-shape problem the web apiFetch already solves. Token comes from a
resolver function so the auth provider can swap implementations
without touching the client.

ErrorCopy is a lazy singleton over assets/error-copy.json with the
same fallback chain web uses (specific code -> unknown -> hard-coded).
2026-05-02 17:15:55 -04:00
bvandeusen 2729ab897f feat(flutter): FabledSwordTheme ThemeExtension + Material 3 ThemeData
Widgets read tokens via Theme.of(ctx).extension<FabledSwordTheme>().
Material 3 ColorScheme bound to FabledSword tokens. Body text uses
GoogleFonts.interTextTheme() so the fonts work without bundling .ttf
files (offline-from-cold-launch is a separate question once #357 lands).
2026-05-02 16:56:38 -04:00
bvandeusen f97cf6eb38 feat(flutter): token + asset sync from web; gen_tokens.dart
shared/fabledsword.tokens.json mirrors web/src/lib/styles/tokens.json;
assets/error-copy.json mirrors the web's error-code copy table;
assets/svg/album-fallback.svg mirrors the web placeholder. Together
these give Flutter the same visual + copy starting point as the SPA.

tool/gen_tokens.dart emits lib/theme/tokens.dart from the JSON; the
generated file is committed so CI can verify the JSON and the Dart
file haven't drifted (gate added in Task 21).

tool/sync_shared.sh mkdirs the shared/ + assets/ destinations before
copying so the script works on a fresh checkout (the original
plan-text shape only mkdir'd assets/svg).

pubspec.yaml assets block uncommented now that the three asset paths
exist on disk.
2026-05-02 16:55:21 -04:00
bvandeusen e46224acc9 feat(flutter): project scaffold with pubspec and smoke test
iOS + Android targets only; desktop disabled (per spec, Tauri-wrapped
SvelteKit handles desktop in v1.1). Riverpod + dio + just_audio +
audio_service + go_router pinned in pubspec. Smoke test confirms the
ProviderScope wiring boots a Material app.

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