Skip to content

Crate: UX fixes, rebrand, accent picker, motion pass - #1

Open
4bit33 wants to merge 4 commits into
masterfrom
feature/phase-0-ux-fixes
Open

4bit33 wants to merge 4 commits into
masterfrom
feature/phase-0-ux-fixes

Conversation

@4bit33

@4bit33 4bit33 commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the rebrand / UX plan in four phases, one commit each.

Phase 0 — correctness fixes (the concrete causes of the "unfinished" feel)

  • Back-stack bug: one navigate() applied popUpTo(Home) to every push, so back from Player/Queue/Search/PlaylistDetail/Favorites always landed on Home. Split into navigateToTab() (dock/rail only) and a plain-push navigate().
  • Library Artist/Genre/Folder rows had no onClick (Albums worked). Wired playArtist/playGenre/playFolder (new GetFolderSongsUseCase + getSongsOfFolder DAO query on the existing relativePath column — no migration).
  • Playlists: delete confirmation on the list screen (detail already had one), "Add songs" inside a playlist (new searchable SongPickerSheet), "Add to playlist" from Home/Search/Favorites rows, and a snackbar instead of a silent no-op when playing an empty playlist.
  • collectAsState()collectAsStateWithLifecycle() everywhere.

Phase 1 — rebrand to "Crate": app_name, permission text, window theme, manifest, rootProject.name, ARCHITECTURE.md title, and a real launcher icon (it was still the stock Android Studio template). Kotlin package, applicationId, resonance.db and Resonance* class names are intentionally unchanged (invisible to users; the DB rename would need a migration).

Phase 2 — user-selectable accent: accent is a hue persisted in DataStore; stitchDarkColors(accentHue) derives the accent tones via android.graphics.Color.HSVToColor while surfaces and the cyan format-badge color stay fixed. Settings → Appearance gets 6 swatches plus a hue slider. Removed the dead vinyl-groove fallback (WaveformFallback, ArtworkFallbackStyle, unused ArtworkFallback).

Phase 3 — motion: NavHost fade transitions; mini player via AnimatedVisibility and hidden on Player/Queue; Modifier.animateItem() on the Library/Search/Favorites/Home/Playlists lists and on ReorderableLazyColumn (fixes the instant snap-back after drag-reorder); crossfaded play/pause icons; animated nav-dock indicator. No new dependencies.

Verification

  • ./gradlew testDebugUnitTest — BUILD SUCCESSFUL: KSP (Room validates the new query), main + test compilation, 105 tests, 0 failures.
  • On-device smoke test (not done yet):
    • Library → song → Player → back returns to Library (also Search→Player, Home→Favorites→Player, Playlists→Detail→Player)
    • Tap an artist / genre / folder row → plays and opens Queue
    • Delete playlist from the list → confirmation; "Add songs" inside a playlist; add-to-playlist from Home/Search/Favorites; Play on an empty playlist → snackbar
    • Settings → Appearance: swatch and slider recolor the app live and persist after restart
    • Launcher shows "Crate" and the new icon
    • Tab switches / mini player show-hide / queue drag-reorder / play-pause icon animate smoothly

🤖 Generated with Claude Code

4bit33 and others added 4 commits September 14, 2026 21:39
The app felt unfinished due to a handful of concrete bugs rather than
vague roughness:

- navigate() applied popUpTo(Home) to every push, including drill-downs
  into Player/Queue/Search/PlaylistDetail/Favorites, so back from those
  screens always landed on Home instead of the screen the user came
  from. Split into navigateToTab() (dock/rail only) and a plain-push
  navigate() for everything else.
- Library's Artist/Genre/Folder rows had no onClick at all, unlike
  Albums. Wired playArtist/playGenre/playFolder (new
  GetFolderSongsUseCase + getSongsOfFolder query, mirroring the
  existing album/artist/genre pattern) so tapping any of them plays
  and opens the queue.
- Playlists list deleted with no confirmation while the detail screen
  did confirm; unified both on the same ResonanceDialog flow.
- Playlist detail had no way to add songs while viewing it; added a
  SongPickerSheet (searchable multi-select) wired to a new
  addSongs() on PlaylistDetailViewModel.
- "Add to playlist" only existed on Library's Songs tab; extended the
  same SongOverflowSheet/PlaylistPickerSheet wiring to Home, Search,
  and Favorites song rows.
- Playing an empty playlist silently no-opped with zero feedback (the
  app had no Toast/snackbar anywhere despite a ready-made
  ResonanceSnackbar component). Wired a shared SnackbarHost into the
  app shell and surfaced it there.

Also switched every collectAsState() to collectAsStateWithLifecycle()
across the app so StateFlow collection pauses while the app is
backgrounded instead of running continuously.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
User-facing rename only — app_name, permission rationale string,
window theme name, manifest label, root project name, ARCHITECTURE.md
title. Kotlin package, applicationId, and internal Resonance* symbol
names are unchanged (deferred, no user-facing benefit for the churn).

Replaced the launcher icon foreground: it was still the unmodified
stock Android Studio template (purple circle, default glyph), never
actually designed. New mark is a simple rounded-square outline with a
play triangle, flat accent color, no gradients — matches the
"simpler/mainstream" direction from the plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Accent color is now a persisted hue (DataStore, same pattern as
themeMode) instead of hardcoded copper. stitchDarkColors(accentHue)
derives accent/accentGlow/accentDim/onPrimaryContainer from the hue
via android.graphics.Color.HSVToColor (no new dependency); surfaces
and the secondary/cyan format-badge color stay fixed per DESIGN.md's
existing "only the primary accent may adapt" rule.

Settings > Appearance gets a swatch row (6 curated hues) plus a hue
slider for a fully custom accent.

Also deleted the vinyl-groove Canvas fallback (WaveformFallback) and
its ArtworkFallbackStyle enum — no caller ever selected it, every
ArtworkImage use already fell through to the plain note-glyph
fallback (NoteFallback), so the etched-groove path and the unused
legacy ArtworkFallback() wrapper were dead code once collapsed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All via native Compose animation APIs already on the classpath, no new
dependency:

- NavHost gets a default fade enter/exit (tab switches and drill-down
  pushes alike — one crossfade avoids picking wrong directionality for
  the two different kinds of navigation happening through one graph).
- Mini player now mounts/unmounts through AnimatedVisibility (slide +
  fade) instead of a hard if, and is suppressed on the Player/Queue
  routes (route-aware, not just song-null-aware) so it stops doubling
  up with the full transport controls already visible there.
- LazyColumn/LazyRow items across Library (all 5 tabs), Search,
  Favorites, Home's recently-played row, and Playlists get
  Modifier.animateItem() so inserts/removes/filtering slide instead of
  jump-cutting.
- ReorderableLazyColumn (shared by Queue and playlist detail) gets the
  same animateItem() on its item wrapper — this is what actually
  answers the "instant snap-back on drop" and "siblings jump instead
  of sliding" complaints, for free, since animateItem()'s placement
  animation is spring-based by default. zIndex (raise dragged row
  above siblings) moved from the row to the wrapper, since it now
  needs to apply at the LazyColumn-item level, not inside it.
- Play/pause icon crossfades (mini player + full player's
  ResonancePlaybackButton) instead of an instant swap; nav dock icon
  tint animates and the dot/label indicator crossfades instead of
  snapping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@4bit33 4bit33 changed the title Fix navigation back-stack, dead Library taps, and playlist UX gaps Crate: UX fixes, rebrand, accent picker, motion pass Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant