- Adaptive icon: the glossy purple S logo, whole image at 94% so the S sits
inside the 33dp safe zone (it reached 34.6dp untouched), edge blended into
the new #0E0B14 background that matches the image's border.
- Themed (monochrome) icon: the S as a solid silhouette.
- Launch screen: only the S on #0E0B14, so the round splash crop shows no
edge of the textured square.
- tools/make_icons.py regenerates all of these from the source PNG.
Verified on the emulator: icon in the app drawer and the launch screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping through songs could leave the player stopped with Next/Previous
only changing the title. Cause: a song whose file is missing on the
server; Navidrome answers /rest/stream with a JSON error marked HTTP 200,
which was cached as the song, so the player failed to read it and stopped.
- Song downloads with a text/JSON/XML Content-Type are refused, so server
error messages are never cached or played.
- SkipUnplayable: on a playback error, remove the song's cached copy,
show a short note and skip to the next song (waits for the rest of the
queue if it is still arriving; at most 3 in a row; not on network errors).
- Next/Previous re-prepare a stopped player.
Verified on the emulator against the owner's server: the broken song is
skipped with a note, the bad cached copy is removed and not re-saved,
10 rounds of rapid next/previous never left the player stopped, and after
an offline failure one tap on Next plays again. 84 unit tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Star and ⋮ beside the title. The star is the same one as in the queue
view. The menu (also used by the queue view) has Add to playlist (own
playlists or a new one), Go to album, Go to artist, Share, and a Sleep
timer (15/30/45/60 minutes with a short fade, or end of song) that runs
next to the player in the playback service.
- Live lyrics between the artist and the seek bar, from Navidrome
(getLyricsBySongId, then getLyrics; .lrc text parsed too): the current
line bright, the next one faded, sliding as the song moves. Tap for a
full-screen view that follows the song and seeks when a line is tapped.
Plain lyrics show a Lyrics button; no lyrics hides the area. Loaded in
the background and cached.
- Up Next card at the bottom (next song, or Autoplay); tap opens the queue.
- AdaptiveColumn keeps everything below the cover and gives things up in
order (lyrics to one line, Up Next card, live line, icon row) so nothing
overlaps; on short screens the cover shrinks and centres.
Checked on the emulator at 1080x2400, 1080x1800 and sideways. 77 unit
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Background colors for album pages and Now Playing used Palette's most
vibrant swatch even when it covered only a sliver of the cover, so
Recovery got red from the small cross in its logo. Now colors covering
under 6% of the cover are ignored and the rest are ranked by area, with a
bonus for colorfulness so a bold colored area still beats plain black.
The gradient's top and bottom use the same hue.
Checked on the emulator: Recovery is slate grey, Hot Fuss steel blue,
Dark Sun still a dusky purple. 4 new unit tests (71 total).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cover keeps its full edge-to-edge size but no longer fades into the
background colour: it ends in a hard edge with a thin light line between
the art and the title area (bottom when upright, right side when
sideways). The darkening that keeps the white text readable now starts
below / beside the cover, so the art itself isn't dimmed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android has no Apple Lossless (ALAC) decoder, so ALAC .m4a files (for
example Eminem's Recovery) "played" with the seek bar moving but no
sound: the player had no decoder for the audio track.
Adds Jellyfin's prebuilt Media3 FFmpeg decoder (GPL-3.0) and turns on
extension renderers in fallback mode, so Android's own decoders are still
used for FLAC, MP3, AAC and so on, and FFmpeg only steps in for formats
Android can't decode.
Checked on the emulator: "25 to Life" now produces a 44.1 kHz audio
track (FFmpeg), FLAC still uses c2.android.flac.decoder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scrobbling (the app never reported plays before):
- Scrobbler listens to the player inside PlaybackService, so plays from
the app, notification, lock screen and with the screen off all count.
- "Now playing" (submission=false) when a song starts playing; a full
scrobble (submission=true) once it has actually played half its length
or 4 minutes, at most once per play (ListenTracker, unit-tested).
- Listens are saved on the phone first with their start time, sent in
batches, and resent when the network returns or at the next app start.
- Deezer previews are never reported.
- Checked on the emulator: now playing sent, listen kept while offline
with its original time, sent 7 s after reconnecting, preview silent.
Home tab (now the start tab):
- Recently Played, Recently Added, Most Played, Favorites (starred albums
and songs) and Rediscover (random, fresh each time Home opens) rows,
each with See all. Large shrinking title, pull-to-refresh, rows load in
parallel behind placeholder cards and hide when empty.
- Rows are defined in HomeSections, so new ones (AI mixes) are one entry.
67 unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Search as you type (waits 350 ms after the last key), library via
OpenSubsonic search3 and Deezer's public API at the same time.
- Empty field shows recent searches: tap to repeat, swipe or X to remove,
Clear all. A search is saved when you press Search or tap a result.
- In your library: Top result card, Songs / Albums / Artists with See all
(paged), tapping and long-press menus as elsewhere.
- Not in your library: Deezer songs and albums minus what you already own
(MusicMatch: artist + title ignoring case, accents, punctuation and
"feat." parts). Songs play their 30-second preview in the normal player
with a "Preview · not in your library" label; previews can't be starred,
don't trigger Autoplay and are left out of queue History. Album pages list
each track as a preview.
- "Play full song" / "Download" on every outside result are disabled with
a coming-soon note through OutsideMusicActions, so a later server feature
can switch them on; Deezer track/album/artist ids and ISRC stay attached.
- Deezer unreachable: library results still show with a small note.
Friendly "No results" state.
- 17 new unit tests (56 total).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Adaptive icon: the whole PNG, untrimmed, is the foreground (mipmap PNGs per
density) on a solid #080812 background. The wave reaches 32.5dp from the
centre of the 108dp layer, inside the 33dp safe zone, so no mask crops it.
- Themed (monochrome) icon: a white brightness mask of the wave.
- Launch screen: a splash-only copy where the grey spotlight behind the wave
fades to #080812, so the round splash crop shows just the wave and its glow
instead of a lit disc with hard edges.
- Removed the old SW logo (syncwave-logo.svg and its vector drawables).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Swipe up on the full-art Now Playing (or tap the queue button) to slide
up the queue view; swipe down or the queue button returns to the art.
Swiping down from the art still closes Now Playing.
- Queue view, top to bottom: current song with star (favourite on the
server) and more menu; Shuffle / Repeat / Autoplay pills; History of
played songs with Clear; Up Next (drag handle to reorder, swipe left to
remove); seek bar; previous / glass play pill / next; Lyrics, Cast and
queue toggle. Background from the album art.
- Autoplay (on by default, remembered): when the last song in the queue
starts, the playback service adds similar songs (getSimilarSongs) or,
failing that, random ones. Works with the app closed.
- The full-art screen's bottom row is now Lyrics / Cast / Queue (shuffle
and repeat moved to the queue view). Lyrics and Cast say "coming soon".
- Go to album / artist from a menu now closes Now Playing first.
- Queue entries are built by one shared MediaItemFactory (controller and
service), carrying album, artist, cover and favourite details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Songs view sends at most two song requests at a time (scrolling, A–Z
jumps and queue building share them), instead of bursts.
- All server requests that are safe to repeat (GET) are retried twice,
after 0.6 s and 1.8 s, when the server answers 502/503/504 or the
connection drops; Retry-After is honoured up to 3 s. Timeouts and
cancelled requests are not retried.
- Plain error messages: e.g. "Your music server is busy or restarting.
Please try again in a moment." instead of "HTTP 503".
- Failed responses are logged (tag SyncWaveHttp: method, path and code
only, never credentials) to diagnose future errors.
- Unit tests for the retry behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Skipping to the next or previous song while paused now starts playing
it, from the app, mini player, notification and lock screen alike: the
media session wraps the player in PlayOnSkipPlayer. Verified on the
emulator with Now Playing, the mini player and system media keys.
- Title is much bigger and uses the full width: it shrinks from 32 sp to
22 sp to fit in up to two lines, and only then scrolls slowly sideways
on one line. Artist name one size larger.
- Play/pause is a purple frosted-glass pill: a blurred copy of the cover
(Android 12+) under translucent brand purple, a soft light edge, a
white icon, and a springy squish when pressed. Other buttons unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Long-press a song, album or artist for Play next, Add to queue, Go to
album, Go to artist, and Shuffle (albums, artists). A confirmation pill
("Added to queue") shows immediately; album/artist songs load behind it.
- Up Next over Now Playing: drag handle to reorder, swipe left to remove,
tap to play. Reorders and removals go straight to the player.
- Shuffle (reorders upcoming songs; off restores their order) and repeat
(off / all / one) buttons on Now Playing.
- Builds and unit tests pass; not yet tested on the emulator or a phone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Artist list rows request 160 px photos instead of 600 px (52 dp
circles); the artist page still uses the large photo.
- Lists declare content types (song, artist, header, track, album) so
rows scrolled off screen are reused instead of rebuilt.
- Measured while flinging 12 times on the emulator (debug build), janky
frames before → after: Songs 2.1% → 0.8%, Artists 0.85% → 0.7%,
Albums grid 0.85% → 0%.
Images already load at their display size and come from Coil's memory
and 250 MB disk caches. Large titles and the album-open transition were
done in earlier 3b work. The Now Playing pause shrink stays off: it
doesn't fit the full-screen artwork layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured tap → first sound on the emulator against the Navidrome demo
(see PlaybackTrace, `adb logcat -s SWTiming`):
before after
album, same song replayed 1.15-1.8 s 0.19-0.24 s (now from cache)
Next button - 0.24 s (next song preloaded)
Songs view: wait before 0.65-0.8 s 0 ms (queue fills in background)
the audio request even went
album, never-played FLAC 1.7 s 1.76 s (unchanged: download of
the file's start)
- ExoPlayer starts after 250 ms of audio (default 2.5 s) and keeps a
30-120 s buffer while playing; 1.5 s after a stall.
- Stream URLs ask for format=raw (no on-the-fly transcoding).
- Tapped song is sent to the player first; the rest of the queue follows
(songs before it are inserted in front). Songs view fetches the next 99
in the background instead of before playing.
- Next song preloads (10 s) for instant, gapless track changes.
- 512 MB on-phone LRU audio cache (Media3 SimpleCache); notification
artwork now comes from Coil's memory/disk cache instead of a download.
- Mini player and Now Playing switch to the tapped song immediately; a
spinner ring appears around play/pause only if audio takes > 0.4 s.
- PlaybackTrace logs each step (tap, service, HTTP, buffering, decoder,
first sound) for future measurements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- syncwave-logo.svg converted to vector drawables: a full-colour adaptive
icon foreground (the three strokes, scaled so the farthest stroke edge
sits inside the 66dp safe zone) on a solid #15132a background.
- Monochrome layer for Android themed icons: both strokes in one colour,
without the background-coloured gap stroke.
- Removed the template's green Android icon and per-density webp files;
minSdk 28 uses the adaptive icon everywhere it's supported.
- Launch screen via androidx.core:core-splashscreen 1.2.0: the logo on
#15132a on every Android version, held until the saved login is read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Detail line under title and artist: genre · year · LOSSLESS badge for
FLAC/ALAC (or format · bitrate for lossy files).
- Shuffle, large Play and Download buttons. Download shows a "coming soon"
toast until offline downloads exist.
- Disc headings (with disc titles when tagged) for multi-disc albums.
- Info block after the tracks: release date (day, month or year as
tagged), songs and running time, record label, and file quality
(format, bit depth / sample rate when available).
- Everything comes from getAlbum tags; anything missing is hidden. ALAC vs
AAC inside .m4a is told apart by bitrate, since the codec isn't reported.
- Long durations now round to the nearest minute (a 4:02 album said 5 min).
- AlbumFacts unit tests; CLAUDE.md updated with today's changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Categories are now Playlists, Artists, Albums, Songs and Downloaded.
Genres is removed from the list; its screens stay in the code, unlinked.
- Downloaded opens an empty state until offline downloads are built.
- Larger category rows: 32dp icons, title-size labels, 68dp rows.
- Recently Added below the list: a two-column grid of the 20 newest
albums (getAlbumList2 "newest") with large covers, name and artist;
tapping one opens its album page with the flying-cover transition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The cover fills the screen edge to edge behind all controls: full width
from the very top when upright, full height on the left when sideways.
Never cropped; a soft shade at the top keeps the status bar and grabber
readable.
- A dark gradient over the lower part (right side when sideways) keeps
the white title, artist, seek bar and controls readable.
- Cover-color tint fills everything the art doesn't cover. It matches the
art's faded edge exactly and only deepens beyond it, so there's no seam.
- Dropped the pause/play shrink and the blurred backdrop: a shrinking
full-screen background would break the edge-to-edge look.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Library tab opens on a list of categories, each opening its own view.
- Artists: A–Z under the server's letter headings, with artist photos
(initial on a purple circle as fallback). Artist page: photo, Play all,
albums in a horizontal row (covers fly to the album page), and every song
on their albums.
- Albums: the grid, now with a Sort menu (Title, Artist, Year newest
first, Recently added). Also reused for a genre's albums.
- Songs: every song A–Z, fetched 100 at a time wherever you scroll, with
same-size placeholders and an A–Z fast-scroll bar (letter bubble,
haptic ticks). Letter jumps binary-search single positions instead of
downloading the whole list, remembering what they've seen. Tapping a
song queues it plus the next 99.
- Playlists: Navidrome playlists, each with cover, length, Play and its
songs in playlist order. Genres: genres with album/song counts, each
opening its album grid.
- Data: new OpenSubsonic calls (getArtists, getArtist, getPlaylists,
getPlaylist, getGenres, more getAlbumList2 options). The title-sorted song
list uses Navidrome's own REST API (OpenSubsonic can't sort songs), kept
in its own client with JWT login and automatic re-login.
- Shared UI pieces: large title with back button, song rows, album cards,
load/retry handling, cover-URL and playback hooks for screens.
- Tests for the letter index, new API parsing and the Navidrome login flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Now Playing switches to a side-by-side layout when the screen is wider
than tall: cover on the left sized to the height, title, seek bar and
previous/play/next on the right. Previously the cover filled the height
and pushed every control below the screen edge.
- Upright, the cover now shrinks when space is short (split-screen, small
phones) so the controls always fit; unchanged on normal phones.
- Album page cover is capped at 55% of the screen height (cropped to a
wide band when sideways) so the title and tracks stay visible; the
collapsing top bar starts fully hidden on these shorter headers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Album pages open with the cover full-width and edge to edge (up behind
the status bar, no border or rounded corners). Its bottom fades into the
album's background color, with title, artist and year over the fade.
- Gentle parallax: the cover scrolls at 65% of the list's speed. The fade
stays fixed while the cover slides under it, so no hard edge appears.
- Floating round back button over the cover; a solid album-colored bar
with the album name fades in, fully opaque by the time the big title
reaches it.
- Page background stays the solid album color down to the cover's bottom
and only deepens below it, safe on any screen height.
- Header loads a 1000px cover and shows the grid's cached 400px cover until
it arrives, so the flying cover never lands on a blank square.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Large titles on Home, Library and Search are one title that scales from
34sp to 20sp and glides into the top bar while scrolling (replacing
Material's two-title cross-fade); the bar still settles fully open or
collapsed. Actions stay in the top row.
- Opening an album: the tapped cover flies from the grid to the album page
(shared-element transition on a spring) while the pages cross-fade, and
flies back on return. The album page shows its title and cover at once
(passed from the grid) while tracks load; the top bar title only fades
in after the header scrolls away.
- Tab switches fade in 150 ms instead of the 700 ms default.
- Now Playing art: shrinks to 84% when paused and springs back with a
single soft overshoot; its shadow lowers and lifts with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- BrandPurple (#6A4DF4, #A693FF on dark backgrounds) is the official accent
for all menus, tabs and lists, also applied to the Android window theme
(launch window, system-drawn accents) in light and dark. Template colors
removed. Dynamic (wallpaper) color stays off.
- Album pages and Now Playing take their background gradient from the
album cover via AndroidX Palette. Colors are darkened only as needed for
white text to meet WCAG 4.5:1, cached per cover, and fade in. Those
screens use ArtworkTheme: white text, light brand purple accents.
- Status bar icons turn light on artwork-colored album pages.
- Unit tests for the color/contrast math.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bottom tab bar (Home, Library, Search) with custom icons and a springy
press effect. Each tab keeps its own history; tapping the current tab
returns to its top screen. Library (album grid) is the start tab; Home
and Search are placeholders. Sign out moves to Home.
- SyncWave theme: neutral surfaces with an electric-violet accent in light
and dark, bold large titles that collapse on scroll. Wallpaper-based
dynamic color is off.
- Floating mini player just above the tab bar on every screen; content
scrolls underneath both bars.
- Now Playing sheet slides up on a spring from the mini player: large art
(shrinks with a bounce when paused), title, artist, custom seek bar with
elapsed/remaining time, previous/play-pause/next. Swipe down (follows the
finger, closes on distance or flick) or Back to close; the screen behind
recedes into a rounded card. Blurred-art backdrop on Android 12+.
- PlaybackController gains seek, previous, position and duration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tapping an album opens its page (cover, details, track list in disc/track
order); tapping a track plays it with the rest of the album queued after.
- PlaybackService (Media3 MediaLibraryService + ExoPlayer) plays in the
background and with the screen off, with notification and lock-screen
controls, audio focus, and pause on headphone unplug. Exported with the
MediaLibraryService/MediaBrowserService filters for Android Auto later;
library browsing returns an empty root until then.
- Streams and notification artwork load through the authenticated OkHttp
client, so credentials never appear in URLs. The session attaches stream
URLs from media ids.
- Mini player (artwork, title, play/pause, next) above the navigation bar
on every screen; the playing track is highlighted on the album page.
- Signing out stops playback and clears the queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Login screen (server URL, username, password) that verifies against the
server with an OpenSubsonic ping before saving.
- Credentials stored encrypted with an Android Keystore AES-GCM key in
DataStore, excluded from backups. Subsonic token auth (salted md5), so the
password is never sent.
- Album grid with cover art (Coil), A-Z, paged 100 at a time, pull to
refresh, sign out.
- Layered packages (model, data/auth, data/subsonic, data/library, ui) with
a manual AppContainer, ready for Media3, Android Auto and the brain service.
- Kotlin 2.4.10 and current AndroidX/OkHttp/Retrofit; fix stray "cla" typo
in the template MainActivity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>