Commit Graph
4 Commits
Author SHA1 Message Date
christopherandClaude Sonnet 5 cf52bb854e Phase 4: Media3 playback engine
Builds the single MediaSession-backed player that in-app playback,
offline downloads (Phase 5), and Android Auto (Phase 6) will all
share, per the Phase 2+ roadmap's Phase 4.

- DeepwavePlaybackService: ExoPlayer + MediaLibrarySession, built as
  a MediaLibraryService from day one (not the plainer
  MediaSessionService) so Android Auto only has to add browse-tree
  content later, never rebuild the service. Browse tree is a stub
  for now (MediaLibrarySession.Callback's defaults deny browsing).
- LocalOrRemoteDataSource: the entire "prefer a downloaded file over
  streaming" mechanism as a ResolvingDataSource.Resolver, backed by
  an in-memory LocalTrackFiles registry that stays empty until
  Phase 5 populates it from Room - reused unmodified by Android Auto
  later.
- ExoPlayer's OkHttpDataSource shares the same signed OkHttpClient as
  Retrofit/Coil, so SubsonicRequestInterceptor signs stream requests
  identically to every other Subsonic call - streamUrl() builds the
  request URL the same way coverArtUrl() does for Coil.
- PlaybackController: app-facing facade over a MediaController,
  exposing Flow<PlaybackUiState> - same facade convention as
  ServerRepository. NowPlayingScreen (new, full-screen: artwork,
  seek bar, play/pause/skip) and MiniPlayerBar (now real, replacing
  Phase 2/3's static placeholder) both consume it.
- Track clicks in AlbumDetailScreen (queues the whole album from the
  clicked index) and SearchScreen (single-song queue) now actually
  play, via PlaybackController injected into their ViewModels.
- Scrobbling: submission=false when a track starts, submission=true
  for the outgoing track on each transition - a simplified heuristic
  rather than a played-percentage threshold, feeding Navidrome's
  play-count data that Phase 3's getAlbumList2(frequent/recent) rows
  read from.
- Runtime POST_NOTIFICATIONS request added to MainActivity for API
  33+ (declared in the manifest but easy to forget the runtime half
  of - without it the service still plays, but its notification
  never shows).

Split MainScreen into a thin PlayerViewModel-resolving wrapper plus a
previewable MainContent, rather than having MainScreen call
hiltViewModel() directly - preserves the interactive Android Studio
preview from last session, which a direct Hilt dependency would have
broken.

media3 pinned to 1.11.1 (built against Kotlin 2.2.0, matching this
project's 2.2.10 - learned from Phase 3's Coil version conflict to
check this before picking a version this time).

Verified extensively on-device against the real Navidrome server:
real tracks play with correct metadata/artwork, pause/resume and
queue auto-advance work, the real Android MediaSession exposes
correct state (checked independently via `dumpsys media_session`,
not just the app's own UI), background playback survives
foregrounding/backgrounding, and no crashes across the whole session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 04:26:55 -04:00
christopherandClaude Sonnet 5 08b3a9a0df Phase 3: Subsonic browsing API + real library UI
Replaces Phase 2's sample-data placeholders with real Navidrome
browsing: artists, albums, songs, and search, per the Phase 2+
roadmap's Phase 3.

- SubsonicApi grows from just ping to getArtists/getArtist/
  getAlbumList2/getAlbum/search3, with DTOs split per-domain
  (ArtistModels/AlbumModels/SongModels/SearchModels) and mapped to
  plain domain models (data/model) by the new LibraryRepository
- Coil wired in for cover art, sharing the same signed OkHttpClient
  as Retrofit via a Hilt EntryPoint (SubsonicRequestInterceptor signs
  cover-art requests identically to every other Subsonic call) -
  Artwork() falls back to the Phase 2 placeholder icon when no cover
  art id is known
- ui/library replaces the Phase 2 placeholders: HomeScreen now shows
  real Recently Added/Played and a random "Made For You" row,
  ArtistsScreen is the real Library tab, ArtistDetailScreen and
  AlbumDetailScreen are new drill-down screens (typed nav routes,
  ViewModels resolve their id via SavedStateHandle.toRoute()), and
  SearchScreen does debounced search3 across artists/albums/songs
- TrackRow gained a `circular` option so it can double as an artist
  row in the Library list, not just a track row

First real test coverage in the repo: LibraryRepositoryTest (DTO to
domain-model mapping against MockWebServer) and
SubsonicRequestInterceptorTest (rewritten URL + signed query params).
CredentialsStore/ServerRepository tests are deliberately still out of
scope - CredentialsStore's Tink/Android Keystore usage isn't testable
in a plain JVM unit test without Robolectric, which felt like a
bigger side quest than this phase called for.

Coil pinned to 3.3.0 rather than the newer 3.6.x latest: newer Coil
requires Kotlin 2.4+, which conflicts with this project's Kotlin
2.2.10; 3.3.0 was the last release built against Kotlin 2.2.x.

Verified end-to-end against a real Navidrome server on-device: Home
rows, artist list/detail, album detail, and search all load real data
and real cover art.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 03:59:53 -04:00
christopherandClaude Sonnet 5 a07868b688 Phase 2: Spotify/SoundCloud-style theme, nav shell, and component library
Redirects Deepwave from a vanilla Subsonic client toward the Spotify/
SoundCloud-style product the user wants, per the Phase 2+ roadmap.

- Dark-first Material3 theme with a fixed aqua accent (no Material You
  dynamic color), full type scale, rounded-corner shape system
- Typed (@Serializable) navigation routes; new bottom-tab shell
  (Home/Search/Library) with a settings entry point and mini-player
  placeholder bar, replacing the single placeholder Home screen
- Logout moved out of Home into a dedicated Settings screen
- Reusable component library (TrackRow, AlbumCard, ArtistCard,
  ArtworkPlaceholder, BottomNavBar, MiniPlayerBar) for Phase 3 to wire
  real data into
- Strings externalized to strings.xml
- Security fix: exclude the Tink keyset and encrypted credentials
  DataStore from Android auto-backup (backup_rules.xml /
  data_extraction_rules.xml), previously unexcluded despite
  allowBackup=true
- Fix: Home screen's content column was missing verticalScroll,
  making it unscrollable once content exceeds the viewport; also
  added breathing room between each row's header and its cards
- Split Login/Settings into stateless content composables + thin
  ViewModel-wired wrappers, and added @Preview coverage (including
  interactive previews for MainScreen and BottomNavBar) across every
  screen and component so the UI can be reviewed in Android Studio's
  preview pane without running on a device

Added material-icons-extended as a pragmatic deviation from the
Phase 2 plan (no new deps) since later phases all need icons outside
Compose's small default set (play/pause/skip/download/playlist).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 03:37:58 -04:00
NetherwarlordandClaude Sonnet 5 1971d02e2b Initial commit: Deepwave Phase 1 - auth scaffolding
Spotify-like Navidrome client scaffold with Hilt DI, a Subsonic API
client (token auth, base-URL rewriting), encrypted single-server
credential storage (DataStore + Tink/Keystore), and a login/session
flow gating Compose Navigation between Login and a placeholder Home
screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 23:38:23 -04:00