Commit Graph
255 Commits
Author SHA1 Message Date
vicrodh 2b6c71000f feat(slint): offline manager — checkboxes + bulk, status to the end, track numbers
Per user feedback:
- Multi-select: a checkbox on every track row + "Select all"/"Clear" toggle +
  a bulk bar ("N selected" / Re-download / Remove). Selection is an in-place
  model edit (offline_manager::toggle_select / set_all_selected) with a live
  selected-count; bulk reads the checked ids and runs redownload_track /
  remove_cached per track.
- Moved the status glyph to the END of the row (albums and tracks) — it read
  as "missing art" at the leading edge under the album-grouping model.
- Track rows now show their position number in the leading slot (filling what
  looked empty). (Position within the album; the catalog track number isn't in
  the cache index.db — a follow-up if the real number is wanted.)

qbz-slint compiles clean.

cc
2026-05-28 12:23:46 -06:00
vicrodh db3d8491f5 feat(slint): offline manager 1:1 — covers, artist rail, sort, failed-only, persisted limit
Closes the v1 gaps in the offline manager toward Tauri parity:

- Album COVERS: rows load the on-disk cover.jpg (per album). Built Send-safely
  — the worker passes the cover PATH (string), the image is decoded on the UI
  thread (slint::Image is not Send).
- Two-pane layout with an A-Z ARTIST RAIL (left, "All artists" + per-artist
  "N albums · M tracks", selectable) + the discography list (right).
- SORT dropdown (A-Z / Recent / Largest / Smallest; Recent uses the DB's
  last_accessed_at DESC order) and a "Failed only" toggle. Filter state lives
  Rust-side (offline_manager) so rebuild can read it off-thread; mirrored back
  onto OfflineManagerState for the UI.
- Limit now PERSISTS to disk (<audio>/offline_limit), restored on activate via
  apply_persisted_limit; the manager's edit-limit writes it.
- Manager mutations (remove/redownload/clear) now rebuild on completion
  (offline_manager::rebuild made pub, called at the end of each cache op) —
  the 500ms timed reload hack is gone.

Deferred (user-accepted): the "track no longer available on Qobuz" case —
offline copy still plays, but warn it can't be re-downloaded / won't work on
Qobuz Connect.

qbz-slint compiles clean.

cc
2026-05-28 11:52:55 -06:00
vicrodh 3e0a718e0c feat(slint): offline cache manager + remove/redownload + Settings Offline page
Slices 3 + 4 of the offline-cache port.

Slice 3 (controller, offline_cache.rs):
- remove_album (maintenance::remove_album_cached_tracks + library rows + live
  row update), redownload_track / redownload_album(failed_only) (via
  select_redownload_targets + reset_track_for_redownload + spawn), clear_all
  (purge), open_folder (xdg-open / open the cache dir).

Slice 4 (manager + Settings):
- OfflineManagerView.slint: a standalone route (NavEntry::OfflineManager ->
  ContentView.offline-manager, mounted in AppShell, mirrors PlaylistManager).
  Stats bar (tracks count / size / limit / usage bar + edit-limit GB input +
  open-folder + clear-all) over a scrolling album-header + track-row list with
  a spinning/check/alert status glyph and per-row re-download / remove.
- offline_manager.rs: builds the album->track rollup + stats from index.db
  (get_all_tracks), pushes [OfflineRow] + stats to OfflineManagerState; set_limit
  (in-memory, session-scoped) + reload_soon (refresh after a mutation).
- OfflineManagerActions wired in main.rs (open/refresh/remove/redownload/
  set-limit/clear-all/open-folder/play).
- Settings gains an "Offline" section (OfflineSettings.slint): Manage offline
  cache (-> the manager), Open cache folder, Clear all. The Local-Library
  rows (show-in-library, repair) arrive with the Local Library port.

qbz-slint compiles clean.

cc
2026-05-28 11:23:55 -06:00
vicrodh a7f2283c92 feat(slint): offline button on every Qobuz track row + cache-whole-playlist
- TrackRow show-download now defaults to true: every Qobuz track row (album,
  artist, label, playlist, mix, search, favorites) carries the offline
  cloud/check button. Non-Qobuz surfaces (future Local Library / Plex) opt out
  by setting it false.
- Playlist header gains a "make offline" CircleAction -> ("playlist","cache")
  -> offline_cache::cache_playlist (get_playlist -> batch its tracks), mirroring
  the album-header action.

qbz-slint compiles clean.

cc
2026-05-28 10:29:43 -06:00
vicrodh 6572d6f33f feat(slint): album-header "Make album offline" + direct button on favorites rows
- offline_cache::cache_album fetches an album's tracks and batches them; wired
  to a new ("album","cache") media-action arm + a cloud-download CircleAction in
  the AlbumPageView header action row.
- Favorites track rows now enable show-download, so the direct per-row offline
  button shows there too (not just album rows / cached tracks).

Rounds out Slice 2: trigger surfaces (per-row button on album+favorites, context
menu everywhere, bulk multi-select, album header), live status (spinner/check/
alert) + seeding, and the unlock padlock. qbz-slint compiles clean.

cc
2026-05-28 09:29:20 -06:00
vicrodh 6bea6a7a0a feat(slint): seed per-row offline status from the index.db on load
A cached track now shows its "ready" check when a view is built, persisting
across navigation within the session (was: only live downloads showed state).

- offline_cache.rs: a session-wide CACHED_IDS set (mirrors fav_cache), seeded
  from index.db ready rows via load_cached_ids() on login/restore, kept in
  sync as downloads complete (mark_cached true) and copies are removed (false).
  is_cached(id) reads it.
- The 7 TrackItem builders seed cache-status from is_cached (favorites/search
  clone/precompute the id to avoid moving it before the borrow).
- auth.rs calls load_cached_ids after the offline cache activates.

qbz-slint compiles clean.

cc
2026-05-28 09:01:06 -06:00
vicrodh 3ffdca729d feat(slint): bulk "Make available offline" in the Library multi-select bar
Adds the 6th MultiSelectBar action -> offline_cache::cache_tracks(selected),
mirroring Tauri's bulk caching. Clears the selection after queuing. (Also
retires the cache_tracks dead-code warning.)

cc
2026-05-28 08:28:47 -06:00
vicrodh b65b402ff1 feat(slint): offline-cache controller + per-row status/unlock plumbing (Slice 2)
The Rust half of the trigger/status UI:

- offline_cache.rs: cache_track / cache_tracks / remove_cached + row_sink — a
  CacheEventSink that reflects download status (queued/downloading%/ready/failed)
  and the unlock padlock onto every visible row, plus terminal toasts. cache_*
  pre-flight the limit, insert the queued row, and spawn the shared
  spawn_track_cache_download with the core client.
- TrackItem gains cache-status / cache-progress / unlocking; set_row_cache_status
  + set_row_unlocking mirror set_row_favorite (walk the 6 track models + search
  hero). All 7 exhaustive initializers updated.
- media-action arms ("track","cache"|"download"|"uncache") dispatch to the
  controller; the play path now passes row_sink so UnlockStart/End fire the
  padlock state during CMAF decrypt.

Compiles clean. Next: the TrackRow.slint affordance (make-offline button + ring
/ check / warn) and the padlock glyph that render these fields.

cc
2026-05-27 21:45:26 -06:00
vicrodh 1413cec626 feat(slint): wire offline cache into the play path (Slice 1 Slint side)
- offline.rs: constructs + owns a per-user OfflineCacheState (same index.db +
  library.db paths as Tauri, so the offline store is shared across frontends);
  activated on login/restore, torn down on logout (hooks in auth.rs).
- playback.rs: play_audible + the gapless trigger now route through QbzCore's
  play_track_resolved / fetch_for_gapless_resolved, so an offline-cached track
  resolves from disk (decrypt -> play_data) before hitting the network. The
  unlock-animation sink is None for now (padlock lands with the Slice 2 UI).

qbz-slint compiles clean. Offline PLAYBACK now works in Slint; the "Make
offline" trigger UI + per-row status + the padlock are next (Slice 2).

cc
2026-05-27 21:22:16 -06:00
vicrodh 81e7d21868 feat(slint): A-Z alpha index for the Artists sidepanel left list
The sidepanel left artist list is now always A-Z grouped (derive_artists
groups when group-enabled OR view-mode==sidepanel; the view toggle re-derives)
with letter headers, and an AlphaStrip on its right edge jumps the list
Flickable proportionally (same approach as the album grouping — not exact,
by design). Replaces the flat ListView.

cc
2026-05-27 16:19:12 -06:00
vicrodh 832f257e5a feat(slint): favorites Artists sidepanel controller (select-artist + view toggle)
Wire the sidepanel backend ahead of its UI: select-artist loads the chosen
artist via artist::load_artist (reusing the /artist/page release_type
classifier per the user's decision) and apply_selected_artist maps its
release sections into selected-artist-sections (DiscoverSections) + total;
a FavoriteArtistAlbum artwork target fills the section covers. The
artists-set-view handler flips grid<->sidepanel (persisted as artists_view)
and clears the selection on the way back to grid. card_to_item is now
pub(crate) for reuse.

cc
2026-05-27 15:40:18 -06:00
vicrodh 41692012ee feat(slint): favorites Artists A-Z grouping + alpha strip
Add the optional A-Z group toggle to the Artists grid (Group: Off/A-Z
QbzSelect, default Off, persisted via favorites_prefs artists_group).
derive_artists now buckets the filtered set into FavArtistSection letter
groups (# first, then A-Z) and builds the alpha-jump model; the grouped grid
renders per-letter headers + sub-grids and an AlphaStrip on the right that
jumps the Flickable proportionally (mirrors the Albums alpha grouping).
Also renames the favorites view header title "Favorites" -> "Library" to
match Qobuz.

cc
2026-05-27 15:32:15 -06:00
vicrodh bc95b30db3 feat(slint): favorites Artists header Shuffle (random artist)
Add a Shuffle CircleAction to the Artists row-2 action cluster that opens a
random artist from the visible set (random ARTIST, not a random album —
matches Tauri). Adds favorites::random_visible_artist + the artists-shuffle
handler routing through invoke_open_artist.

cc
2026-05-27 14:58:55 -06:00
vicrodh b87489206a feat(slint): favorites Artists search + render the filtered set
Point the artists grid at artists-visible (the search-filtered model) and
add the "No artists match your search" empty state. Add the Artists row-2
toolbar branch with the shared ExpandableSearch wired to artists-search +
search-artists -> derive_artists.

cc
2026-05-27 14:41:25 -06:00
vicrodh 70083cf33d feat(slint): favorites Artists grid card fidelity + derive_artists
Drop the albums_count line from the artist card (Tauri #169: Qobuz's count
includes compilations/tributes and is misleadingly high) — removed from
ArtistCard, map_artist, FavoriteArtistItem and FavArtistCard. Shrink the card
image to a 120px circle to match Tauri. Add derive_artists (name-substring
search filter → artists-visible/artists-shown), seeded on load; grouping +
the alpha strip layer on next.

cc
2026-05-27 14:35:49 -06:00
vicrodh a75911b09b feat(slint): favorites Playlists grid/list toggle + List view (persisted)
Add the user-requested grid/list switch to the Playlists toolbar
(parameterized the shared ViewToggle so albums + playlists both drive it)
and a new PlaylistListRow (collage thumb + name + owner·tracks + play +
overflow menu: play/play-next/queue/favorite), bound to playlists-view-mode.
The view mode persists via favorites_prefs (playlists_view). Tauri ships
grid only — the list view is a labeled Slint-better addition.

cc
2026-05-27 14:21:27 -06:00
vicrodh 120670147c feat(slint): favorites Playlists actions — play/queue/share/favorite + sub-tab/search
Wire the playlist card media-actions: play / play-next / queue fetch the
playlist's tracks (core get_playlist) and route to playback play/enqueue;
share copies the Qobuz web-player playlist URL to the clipboard; favorite
toggles the LOCAL QBZ favorite (Library un-favorites in place and drops the
row, Following adds to Library — per user decision). Wire the sub-tab switch
and the name|owner search to derive_playlists. Add share::qobuz_playlist_url
and favorites::remove_playlist_row.

cc
2026-05-27 14:11:31 -06:00
vicrodh 2d2f65a1cb feat(slint): favorites Playlists two-source load (Library + Following)
Replace the single merged get_user_playlists load with the two real
sources mirroring Tauri: Following = playlists the user follows but does
not own (get_user_playlists filtered owner.id != current_user_id);
Library = locally-favorited ids (SQLite get_favorite_playlist_ids) in
favorited order, intersected with the fetched set and falling back to a
single get_playlist for ids not present. Both map to the rich
SearchPlaylistItem (4-cover collage) via search::map_playlist/playlist_item,
replacing the single-cover PlaylistCard. derive_playlists picks the active
sub-tab source and applies the name|owner search filter; the 4-cover
artwork pipeline (FavPlaylistCover{following,index,slot}) fills the collage
and propagates into the search-filtered visible model. The tab badge now
counts the local Library set. Adds library_db::current_user_id.

cc
2026-05-27 13:43:12 -06:00
vicrodh 5240cf6e41 feat(slint): search box for the favorites Labels tab
Add an ExpandableSearch to the Labels toolbar (mirrors tracks/albums),
wired to labels-search + the new derive_labels controller fn, which
forks a name-filtered clone of the loaded labels and shares the full
model when the query is empty (artwork stays live). apply_favorites now
derives the visible set on load.

cc
2026-05-27 13:19:33 -06:00
vicrodh 095be4ff57 feat(slint): carry the dropped favorites-label image through the data path
The favorites-labels wire item (LegacyLabelDto) carries an `image`
field that FavLabel silently dropped, so label cards had no artwork.
Parse it (bare string or the object form, via label::extract_label_image),
plumb it onto LabelCard + FavoriteLabelItem, and issue a FavoriteLabel
artwork job + apply arm mirroring favorite artists. Align the count line
to "{n} albums" (matches Tauri and the sibling artist card) and seed
labels-visible for the upcoming search filter.

cc
2026-05-27 13:09:27 -06:00
vicrodh 39c8e86d7b fix(slint): favorites covers stayed grey under sort/group
The artwork pipeline wrote a decoded cover into the SOURCE model
(FavoritesState.albums / .tracks) by index, but the views render the
derived albums-visible / albums-grouped / tracks-visible models, which
are independent clones whenever a sort/group/search is active. So a
late-arriving cover never reached the rendered card (grey until a
re-derive). Add favorites::set_album_artwork / set_track_artwork to also
propagate the cover by id into the rendered model(s); apply_artwork calls
them for FavoriteAlbum / FavoriteTrack. The disk cache was already in use;
this was purely the render target.

cc
2026-05-27 11:45:43 -06:00
vicrodh dbd6d2e4b5 feat(slint): A-Z jump strip for favorites tracks + albums grouping
Add an AlphaStrip overlaid on the right edge when grouping
alphabetically: tracks "Group: Name" (jumps the virtualized ListView to
the first row of a letter — accurate, uniform 50px rows) and albums
"Group: A-Z" (jumps the grid Flickable proportionally by section ordinal
— approximate, always lands the section in view, per the agreed
ArtistView-style behavior). derive_tracks/derive_albums build the
per-letter jump model (tracks-alpha / albums-alpha).

cc
2026-05-27 10:38:34 -06:00
vicrodh 7f0c227084 feat(slint): seed favorites tab counts up front
Favorites tab badges previously showed a count only after visiting each
tab (the per-tab load set its own total). Add favorites::load_counts —
cheap limit=1 probes per type + the playlist count — applied to
FavoritesState once at startup, so all five badges in the new tab menu
are populated before the user opens a tab. A tab's own load still
refreshes its count.

cc
2026-05-27 09:41:59 -06:00
vicrodh 07c71adb65 feat(slint): favorites bulk Play next / Add to queue / Add to playlist
Extend the favorites tracks bulk bar beyond Remove: Play next and Add to
queue build the queue from the already-loaded Track objects (new
playback::enqueue_tracks, no re-fetch; play-next inserts in order), and
Add to playlist opens the picker for the whole selection. The picker
gains track-ids for bulk add (open_multi); on_pick adds every id via the
existing add_tracks_to_playlist. Make-offline stays out (no Slint
offline-cache path yet).

cc
2026-05-27 08:41:12 -06:00
vicrodh 8355f18e0a feat(slint): persist favorites toolbar choices across restarts
New favorites_prefs module stores albums view/sort/group + tracks group
in <data-dir>/qbz/favorites_ui.json (search stays transient). Loaded into
FavoritesState on each favorites navigation (before the data applies),
saved on every toolbar change; apply_favorites now re-derives on load so
the restored sort/group take effect (also fixes a latent case where a set
group mode wasn't applied after a tab reload). Album view toggle routes
through an albums-set-view action to persist it.

cc
2026-05-27 08:27:34 -06:00
vicrodh 4dddd36073 feat(slint): per-context genre filter + favorites genre filtering
Make genre_filter.rs selection per-context (HashMap keyed by "discover" /
"favorites" + a current context the popup edits), so favorites filters
independently from Discover. Discover keeps its server-side path
unchanged (reads filter_ids("discover") explicitly; its genre buttons set
the "discover" context on open). Favorites get a genre button in both
toolbars that opens the shared popup in the "favorites" context, plus the
popup overlay; derive_tracks / derive_albums filter client-side by the
selected genre NAMES (selected_names expands parents to descendants).
on_toggle / on_clear branch on the current context to re-derive favorites
instead of re-fetching Discover. AlbumSummary gains an optional `genre`
(favorites track album objects carry it; TrackItem can't, so a track
id->genre map backs the tracks filter). Persisted per-context (legacy
flat selection migrates to "discover").

cc
2026-05-27 08:03:56 -06:00
vicrodh 9c273119a6 feat(slint): favorites load error + retry state
navigate_favorites stores the load error in FavoritesState.load-error
(cleared on each load attempt); the view shows a "Couldn't load
favorites" + detail + Retry block when set, and the tracks/albums tabs
are gated so the error replaces them instead of showing alongside an
empty state. Retry re-navigates the current tab.

cc
2026-05-27 07:40:22 -06:00
vicrodh 2fce47bd06 feat(slint): favorites tracks group-by (Off / Album / Artist / Name)
Add a group QbzSelect to the Tracks toolbar. derive_tracks reorders the
visible rows by the group key (album+title, artist+album+title, or
title) so a group's tracks sit together. Visible group-header rows are
deferred until the list is virtualized (the list is a plain for today);
this is the group-ordering half, matching how Tauri's tracks tab uses
grouping as the ordering mechanism.

cc
2026-05-27 07:13:07 -06:00
vicrodh 39b7b6d2f1 feat(slint): favorites albums group-by (Off / A-Z / Artist)
Add a group QbzSelect to the Albums toolbar. derive_albums buckets the
filtered set by artist name or by the title's first letter (# for
non-alphabetic), sections ordered alphabetically, rendered through the
shared AlbumCollectionView grouped path. albums-shown carries the
post-filter count so the count line + no-match state stay correct in
grouped mode.

cc
2026-05-27 07:05:17 -06:00
vicrodh 66a9f9da22 fix(slint): favorite state on album cards + un-favorite in place
AlbumCardItem gains is-favorite (filled accent heart on the card +
"Remove from favorites" in the list-row menu) — favorites albums set it
true so the heart shows its state, which it never did before.

Un-favoriting from the favorites list (track heart or album card heart)
now removes the item: the row/card fades out (new `removing` field drives
a 250ms opacity animation on TrackRow / AlbumCard / AlbumListRow), the
favorite is removed on the server (+ fav_cache for tracks), and the row
is dropped from both the full and visible models after the fade. All
TrackItem / AlbumCardItem initializers carry the new fields.

cc
2026-05-27 06:56:26 -06:00
vicrodh 57bace3e1d feat(slint): favorites albums search + sort + shuffle
Add an expandable search (title / artist), a sort QbzSelect
(Default / Title A-Z / Title Z-A / Artist A-Z) and a Shuffle header
action (play a random visible album) to the Albums tab. albums-visible
derives from the full albums set via derive_albums (shared model on the
no-filter/default fast path, filtered+sorted clone otherwise — the
label.rs::derive_releases pattern), and the count reflects the filter.

cc
2026-05-26 23:09:17 -06:00
vicrodh b4b11a59e5 feat(slint): favorites albums list view + grid/list toggle
Replace the bare favorites albums grid with the shared
AlbumCollectionView plus a grid/list segmented toggle and a count line —
adding the list view that was missing (the AlbumListRow extras now fill
via the shared album_map). Album card actions (play / queue / favorite /
go-to) route through a new album-action callback to the album
media-action arms; the artist link still opens the artist.

cc
2026-05-26 22:46:52 -06:00
vicrodh e9836184e7 feat(slint): favorites tracks multi-select + bulk remove
Add a select toggle to the Tracks header that enters multi-select mode
(per-row checkboxes via the shared TrackRow) and shows the shared
MultiSelectBar with Select all / Remove from favorites / Clear. Selection
lives on the rendered tracks-visible rows (mirrors playlist.rs); the
toggle-select router arm and recount gain a Favorites case. Bulk remove
loops remove_favorite, syncs fav_cache, then reloads the tab. Play next /
queue / add-to-playlist / make-offline are deferred (each needs a backend
path: batch enqueue / multi-id picker / offline cache).

cc
2026-05-26 22:40:06 -06:00
vicrodh 43b0c28aee feat(slint): favorites tracks Play all + Shuffle header actions
Retain the loaded favorite tracks as a play-ready Vec<Track> and add
Play all + Shuffle CircleAction buttons by the title (Tracks tab only),
wired to playback::play_tracks. Mirrors the Mix/Playlist transport
(those expose exactly these two; no batch play-next path exists yet).

cc
2026-05-26 22:28:19 -06:00
vicrodh 708fd50c87 feat(slint): favorites tracks search + results count
Add an expandable search over the loaded favorite tracks (title /
artist / album) plus a results-count line. tracks-visible (what the list
renders) derives from the full tracks set: an empty query shares the
model so artwork stays live, a query forks a filtered clone carrying the
already-decoded artwork (the LabelState albums/visible pattern). Adds a
no-match empty state.

cc
2026-05-26 22:18:02 -06:00
vicrodh d5aa3c26d8 feat(slint): page favorites to exhaustion instead of one 500 page
load_favorites looped a single 500-item fetch; users with larger
libraries silently lost everything past the first page. Page through
get_favorites (offset += page len) until a short page or offset >= total,
capped at 10k (mirrors Tauri's fetchAllFavorites). Applies to tracks,
albums, artists and labels.

cc
2026-05-26 21:39:41 -06:00
vicrodh 8dedc2a958 feat(slint): favorites tracks show album column + favorite heart
map_track now carries the album title (was dropped, blanked as ""), and
the Favorites tracks rows enable show-album + show-favorite so the album
column and the un-favorite heart render — matching the Tauri favorites
tracks row. Offline-cache download button stays off until that path is
wired.

cc
2026-05-26 21:35:35 -06:00
vicrodh d80f9c73b5 refactor(slint): extract shared album_map module
Move map_album (V2 nested-first with flat fallback), tier/tier_hires,
release-type classification, to_item and sort_album_items out of
label.rs into a shared album_map module. favorites.rs now maps albums
through it too, so favorite album cards gain the AlbumListRow extras
(release_type / quality_detail / track_count / plain_year) and the
hires-without-bit-depth tier fallback — one implementation to maintain
ahead of the Favorites album list/grid toolbar port.

cc
2026-05-26 21:12:57 -06:00
vicrodh 12048bcee0 fix(slint): label albums missing artist/quality/date + favorite + load-more
/label/getAlbums returns the V2 nested album shape (DiscographyAlbumDto:
artists[], audio_info, dates, track_count) — confirmed against the Android
decompiled sources — but it was deserialized into the flat Album model,
which dropped artist, quality and release date (Unknown Artist in
group-by-artist, no quality badge, no date, empty list-row columns).

qbz-models Album: capture the nested V2 fields (audio_info, dates,
track_count, release_type) as optional serde(default) — backward
compatible with the flat-shape endpoints.
label.rs map_album: read nested-first (quality from audio_info, date from
dates, count from track_count, artist from artists[] main-artist, explicit
release_type) with flat fallback. Fixes the releases grid + list columns,
the landing Releases carousel, and Critics' Picks.
Wire ("album","favorite") -> add_favorite + toast (works on every album
card via the shared component).
LabelReleases load-more now driven by has_more (the endpoint caps each
page well below the catalog, so total was an unreliable gate).

cc
2026-05-26 20:10:09 -06:00
vicrodh 56c924d8ea feat(slint): Label landing page + All Releases sub-view
Port the Tauri LabelView module to native Slint, reusing existing
components and extending where needed (no duplication).

Landing (new LabelPageView): circular header + description read-more +
Follow toggle, sticky JUMP TO bar, Popular Tracks (multi-select, 5->20->50
reveal, Play all), and Releases (+ See all) / Critics' Picks / Playlists /
Artists / More Labels carousels.

All Releases sub-view (LabelReleasesView): circular header + album count,
toolbar (local search, group-by-artist, Hi-Res filter, sort) + grid/list
toggle, load-more, states. TYPE column now populated.

Split the label route: ContentView::label -> LabelPageView (landing),
ContentView::label-releases -> LabelReleasesView; add NavEntry::LabelReleases.
label.rs load_label_page parses /label/page + getAlbums + explore + the
favorite-labels set; derive_releases re-derives the sub-view list.
Reuse Carousel/PlaylistCarousel/ArtistCarousel/TrackRow/AlbumCard/
AlbumListRow/JumpNavBar/QbzSelect/ExpandableSearch; extend ArtistCard/
ArtistCarousel (follow-mode/follow-kind); promote CircleAction to a shared
primitive; JumpNavBar gains show-search. Wire label media-actions:
follow, play-top, open (more-labels), see-all.

cc
2026-05-26 18:35:42 -06:00
vicrodh b2004e6867 feat(slint): TrackRow favorites + artist/album links, PlaylistView, LabelView, scrollbar
CHECKPOINT — this batch is NOT yet acceptable; substantial feedback is pending. Committed so the work is preserved for rework next session, after a max-effort Tauri code review. Areas: TrackRow favorite toggle/state + clickable artist/album (TrackItem gains is-favorite/artist-id/album-id + fav_cache, populated across all builders); PlaylistView header buttons + cover-art hover overlay; LabelView full AlbumCard + grid/list toggle; vertical scrollbar moved to the right gutter across ~12 views.

cc
2026-05-26 16:36:02 -06:00
vicrodh 178d135328 feat(slint): Playlist Manager + search most-popular hero polish
Playlist Manager: list view renders compact rows with total play time; folder grid cards no longer clip the playlist count (content-driven height); list view shows compact folder chips; the move-to-folder menu gains a folder search and a 4-row scroll cap. Search: the most-popular track hero is resized to the artist-card height with the quality shown as centered text instead of a clipped badge.

cc
2026-05-26 15:09:29 -06:00
vicrodh d30e3d658d feat(slint): sidebar context menus, in-app toast, SearchView + Playlist Manager polish
Sidebar:
- Fix "Recursion detected" panic from focusing the playlist search input in init (defer via a single-shot Timer).
- Right-click context menus: playlist (Edit, Hide from sidebar, Move to folder with folder search + Move to root) and folder (Edit, Hide from sidebar), built on ContextMenu/ContextMenuItem; remove the unreachable hover "..." button.
- Filter hidden playlists/folders out of the list; add folders::set_folder_hidden.
- Folder list in the move-to-folder menu scrolls (cap 5 rows); close-on-click-outside so the folder-search input no longer dismisses the menu.
- Sort-menu Refresh now uses a dedicated refresh-cw icon.

Toast: in-app ToastKind/ToastState + Toast overlay mounted at the app root, crate::toast (show/hide, per-kind auto-hide via a reusable Timer, cross-thread *_weak helpers); surface play album/artist load failures.

SearchView: title inline with NavButtons + searchType filters floated right; tab strip on its own row; Most-popular hero for a track uses the new SearchTrackHero, sized to the artist card.

Playlist Manager: list view renders compact PmListRow rows instead of cards; grid/folder card fonts tightened to match Tauri; folder card centered with hover-reveal edit; search input narrowed to 70%.

cc
2026-05-26 12:21:59 -06:00
vicrodh 2bf6feaa6d feat(slint): Playlist Manager view + shared nav/state/shell wiring
Port the Playlist Manager: header/toolbar (search/filter/sort/folder-flat
toggle/view-mode/new-folder/counter), FOLDERS card grid (accent icon, count,
edit), PLAYLISTS grid/list/tree, folder CRUD (FolderEditModal), move-to-
folder, per-card actions, and arrow-button reorder (custom sort). Reached
from the sidebar "Manage playlists" item; backend 100% reused via
QbzCore/library.db (no new commands). Pointer-event drag-and-drop and
gradient folder colors are deferred follow-ups.

This commit also carries the cross-cutting wiring the session's features all
share — state.slint globals/structs, NavEntry variants, main.rs dispatch +
action wiring, artwork targets, app.slint exports, AppShell mounts — so HEAD
is the verified-green tree (cargo check + release build clean).

cc
2026-05-26 01:07:08 -06:00
vicrodh 9f70a5ac5a feat(slint): sidebar Playlists toolbar + folders + micro-collage refinements
PLAYLISTS header: smaller title, recursive search (hides title), create
modal at 1:1 with Tauri (name/description/folder/public), sort menu, and a
collapse chevron. Folders: accent-tinted icon, open/close chevron as the
primary affordance (no more purposeless "..."), count shown on hover only.
Playlists render a 2x2 micro-collage of track covers (opt-out) instead of
the glyph, sourced from the existing playlist images (no extra fetch).

cc
2026-05-26 01:07:08 -06:00
vicrodh c5c124fb0c feat(slint): Discover "View all" pages + reusable AlbumListRow + recently-played metadata
Per-module discover See-All pages (new releases, press accolades, ideal
discography, qobuzissimes, albums of the week, most streamed) reachable
from each carousel's "View all" link: a paginated grid OR list view with
client-side search, genre filter, and a fixed header. Adds a reusable
AlbumListRow (columns #/ITEM/TYPE/SOURCE/QUALITY/TRACKS/YEAR, SOURCE
hideable, reuses QualityBadgeFull) with play-on-hover + even/odd zebra.
Recently Played albums now carry genre + localized release date + quality
(captured at play time, no extra fetch). Shared home::map_album /
card_to_item reused; grid-only AlbumCardItem literals get ..Default.

cc
2026-05-26 01:07:08 -06:00
vicrodh 9f1e036308 feat(slint): album cards show genre + localized full release date (#469)
Album-card hover overlay now shows the full release date as "MMM D, YYYY"
(e.g. "Nov 6, 2025") instead of the year alone. New dates::release_label
helper keeps the month/day/year order fixed but localizes the month via
chrono format_localized (the app ships 5 languages; current_locale() is the
single hook to thread the real UI language once Slint wires translations —
English for now). Applied to the Discover and Favorites album mappings.

Also fixes Favorites > Albums cards, which hardcoded an empty genre and a
year-only label: they now read album.genre and the full date, matching the
Discover cards 1:1. The data was already in the getUserFavorites response
(qbz-models Album parses genre + release_date_original) — no backend change.

cc
2026-05-25 17:42:36 -06:00
vicrodh 21af0f63f8 feat(slint): favorites tabs as history pages + homologated nav-bar/title
Each favorites tab is now its own history entry (NavEntry::Favorites{tab},
mirroring Discover) recorded from both the header nav and the in-view tab
bar, so back/forward moves between tabs instead of exiting Favorites. Add
the back/forward NavButtons at the same position as every other page
(11px top padding + 22px gap) — FavoritesView was the only view missing
it — and shrink the "Favorites" heading to Typography.section (the album
name size in AlbumView).

cc
2026-05-25 17:41:35 -06:00
vicrodh 11a78ed2f6 feat(slint): resolve Auto backend to a concrete one in audio settings (#470)
Port #470's UI behavior: add an "Auto" entry at the top of the backend
dropdown that, on select, resolves to the best available backend (PipeWire
if present, else System) and persists that concrete value. The rebuilt
snapshot then moves the dropdown onto the resolved backend, so backend_type
is never left null/Auto. Concrete backends keep mapping by their shifted
index; System wiring comes from available_backends() (merged backend half).

cc
2026-05-25 17:05:44 -06:00
vicrodh de826bf560 feat(slint): playlist detail polish, sort (incl. custom) + WIP track drag
Polish + consistency:
  - Compact playlist header (cover 150px; nav->header spacing
    homologated with AlbumView); description truncated to ~2 lines +
    "Read more" modal; custom-artwork buttons only on cover hover.
  - New Typography.section (18px) token for hero/section titles,
    applied across Album / Artist / Mix / Playlist + Home carousels.
  - Shorter track rows app-wide (50px row, 36px artwork, -1 fonts);
    +3px column-header/divider gap; wider track-number column.
  - Reusable ExpandableSearch (the JumpNavBar page-search pattern:
    magnifier -> opens left -> X to close) in the playlist action row.
  - Custom ListScrollbar (thicker, min thumb height, own gutter) since
    Slint's ListView scrollbar can't be styled.

Sort:
  - Default / Title / Artist / Album / Duration with asc/desc per
    field, plus Custom (manual order persisted in library.db with
    up/down reorder). Client-side search + sort combined.

WIP: track drag-and-drop onto sidebar playlists
  - DragState/DragActions globals, drag source on the shared TrackRow,
    follow-cursor DragGhost, sidebar playlist rows self-detect the drop
    via absolute-position, multi-select group drag, add_tracks_to_playlist
    on drop, Flickables freeze scroll during a drag.
  - KNOWN ISSUE: the ghost is offset from the cursor (the row
    absolute-position + pointer mapping is off, esp. inside Flickables).
    Diagnostic logging is left in (TrackRow debug() + a start log) to
    pin down the wrong term before the generic fix.

cc
2026-05-22 18:26:55 -06:00