feat(locallibrary): Folders tab (flat) — folder-grouped album grid

Fills the Folders tab: a full-loaded grid of albums grouped by directory
(get_albums_with_full_filter), via the shared AlbumCollectionView (grid +
source badge), covers loaded from disk, loaded once on first visit. Clicking
a folder album opens its detail + plays it — navigate_local_album and
play_local_album now resolve tracks via fetch_album_tracks_blocking, which
tries the metadata grouping (Albums tab) then the folder grouping (Folders
tab), so both album views work. New ArtworkTarget::LocalFolderCard applies
covers to the folders model.

Tree mode (two-pane lazy folder tree) + folder search/sort are later slices.

cc
This commit is contained in:
vicrodh committed 2026-05-29 07:37:47 -06:00
1 parent 79551cebb7
commit df351ec6f6
6 files changed
+162 -5

No files matched your search

@@ -536,9 +536,71 @@ export component LocalLibraryView inherits Rectangle {
}
}
// -------- Folders tab (flat) — folder-grouped album grid -----
if LocalLibraryState.active-tab == "folders": Rectangle {
if LocalLibraryState.folders-loading: VerticalLayout {
alignment: center;
HorizontalLayout {
alignment: center;
LoadingSpinner { size: 36px; }
}
}
if !LocalLibraryState.folders-loading
&& LocalLibraryState.folders.length == 0: VerticalLayout {
alignment: center;
Text {
text: @tr("No folders in your local library yet.");
color: Theme.text-muted;
font-size: Typography.body;
horizontal-alignment: center;
}
}
if !LocalLibraryState.folders-loading
&& LocalLibraryState.folders.length > 0: Rectangle {
folders-flick := Flickable {
viewport-width: self.width;
viewport-height: folders-page.preferred-height;
folders-page := VerticalLayout {
width: parent.width;
padding-left: 32px;
padding-right: 32px;
padding-top: 16px;
padding-bottom: 100px;
alignment: start;
AlbumCollectionView {
albums: LocalLibraryState.folders;
is-grouped: false;
view-mode: "grid";
show-source: false;
show-favorite: false;
show-source-badge: true;
open-album(id) => {
LocalLibraryActions.open-album(id);
}
open-artist(id) => {
LocalLibraryActions.open-artist(id);
}
media-action(kind, id, action) => {
LocalLibraryActions.album-action(id, action);
}
}
}
}
ListScrollbar {
x: parent.width - self.width - 4px;
width: 14px;
height: 100%;
viewport-height: folders-flick.viewport-height;
visible-height: folders-flick.height;
viewport-y <=> folders-flick.viewport-y;
}
}
}
// -------- Other tabs — placeholder until their slices land ----
if LocalLibraryState.active-tab != "albums"
&& LocalLibraryState.active-tab != "tracks": VerticalLayout {
&& LocalLibraryState.active-tab != "tracks"
&& LocalLibraryState.active-tab != "folders": VerticalLayout {
alignment: center;
spacing: 6px;
Text {