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
This commit is contained in:
5 files changed
+114
-4
No files matched your search
@@ -367,6 +367,8 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
|
||||
state.set_tracks_visible(model);
|
||||
state.set_tracks_total(total as i32);
|
||||
state.set_tracks_search("".into());
|
||||
// Apply the (persisted) group mode to the freshly loaded set.
|
||||
derive_tracks(window);
|
||||
}
|
||||
FavData::Albums { items, total } => {
|
||||
// Everything in the Albums tab is a favorite -> filled heart.
|
||||
@@ -388,8 +390,8 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
|
||||
state.set_albums_total(total as i32);
|
||||
state.set_albums_shown(n);
|
||||
state.set_albums_search("".into());
|
||||
state.set_albums_sort_by("default".into());
|
||||
state.set_albums_group_mode("off".into());
|
||||
// Apply the (persisted) sort + group to the freshly loaded set.
|
||||
derive_albums(window);
|
||||
}
|
||||
FavData::Artists { items, total } => {
|
||||
let cards: Vec<FavoriteArtistItem> = items
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Persistence for the favorites toolbar choices (albums view / sort /
|
||||
//! group + tracks group) across restarts. A small json under
|
||||
//! `<data-dir>/qbz/favorites_ui.json`, mirroring the genre_filter store.
|
||||
//! Search queries are transient and deliberately not persisted.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use crate::{AppWindow, FavoritesState};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Prefs {
|
||||
#[serde(default = "d_grid")]
|
||||
albums_view: String,
|
||||
#[serde(default = "d_default")]
|
||||
albums_sort: String,
|
||||
#[serde(default = "d_off")]
|
||||
albums_group: String,
|
||||
#[serde(default = "d_off")]
|
||||
tracks_group: String,
|
||||
}
|
||||
|
||||
impl Default for Prefs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
albums_view: d_grid(),
|
||||
albums_sort: d_default(),
|
||||
albums_group: d_off(),
|
||||
tracks_group: d_off(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn d_grid() -> String {
|
||||
"grid".to_string()
|
||||
}
|
||||
fn d_default() -> String {
|
||||
"default".to_string()
|
||||
}
|
||||
fn d_off() -> String {
|
||||
"off".to_string()
|
||||
}
|
||||
|
||||
fn store_path() -> Option<PathBuf> {
|
||||
Some(dirs::data_dir()?.join("qbz").join("favorites_ui.json"))
|
||||
}
|
||||
|
||||
fn read() -> Prefs {
|
||||
let Some(path) = store_path() else {
|
||||
return Prefs::default();
|
||||
};
|
||||
match std::fs::read(&path) {
|
||||
Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(),
|
||||
Err(_) => Prefs::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the persisted toolbar choices to `FavoritesState`. UI thread.
|
||||
pub fn load(window: &AppWindow) {
|
||||
let p = read();
|
||||
let st = window.global::<FavoritesState>();
|
||||
st.set_albums_view_mode(p.albums_view.into());
|
||||
st.set_albums_sort_by(p.albums_sort.into());
|
||||
st.set_albums_group_mode(p.albums_group.into());
|
||||
st.set_tracks_group_mode(p.tracks_group.into());
|
||||
}
|
||||
|
||||
/// Persist the current toolbar choices read from `FavoritesState`.
|
||||
pub fn save(window: &AppWindow) {
|
||||
let Some(path) = store_path() else {
|
||||
return;
|
||||
};
|
||||
let st = window.global::<FavoritesState>();
|
||||
let p = Prefs {
|
||||
albums_view: st.get_albums_view_mode().into(),
|
||||
albums_sort: st.get_albums_sort_by().into(),
|
||||
albums_group: st.get_albums_group_mode().into(),
|
||||
tracks_group: st.get_tracks_group_mode().into(),
|
||||
};
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if let Ok(json) = serde_json::to_vec_pretty(&p) {
|
||||
let _ = std::fs::write(&path, json);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ mod discover_browse;
|
||||
mod discovery_dismiss;
|
||||
mod fav_cache;
|
||||
mod favorites;
|
||||
mod favorites_prefs;
|
||||
mod foryou;
|
||||
mod genre_filter;
|
||||
mod home;
|
||||
@@ -784,6 +785,9 @@ fn navigate_favorites(
|
||||
// toolbar genre button shows the favorites selection count.
|
||||
genre_filter::set_context("favorites");
|
||||
genre_filter::apply_state(&w);
|
||||
// Restore persisted toolbar choices before the data applies +
|
||||
// derives, so the loaded view honors them.
|
||||
favorites_prefs::load(&w);
|
||||
});
|
||||
match favorites::load_favorites(&runtime, tab).await {
|
||||
Ok(data) => {
|
||||
@@ -4346,6 +4350,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_albums_sort_by(s);
|
||||
favorites::derive_albums(&w);
|
||||
favorites_prefs::save(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
// Albums grid/list view toggle (persisted).
|
||||
let weak = window.as_weak();
|
||||
window
|
||||
.global::<FavoritesActions>()
|
||||
.on_albums_set_view(move |v| {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_albums_view_mode(v);
|
||||
favorites_prefs::save(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4358,6 +4375,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_albums_group_mode(g);
|
||||
favorites::derive_albums(&w);
|
||||
favorites_prefs::save(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4480,6 +4498,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_tracks_group_mode(g);
|
||||
favorites::derive_tracks(&w);
|
||||
favorites_prefs::save(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -681,14 +681,14 @@ export component FavoritesView inherits Rectangle {
|
||||
icon: @image-url("../assets/icons/layout-grid.svg");
|
||||
active: FavoritesState.albums-view-mode == "grid";
|
||||
clicked => {
|
||||
FavoritesState.albums-view-mode = "grid";
|
||||
FavoritesActions.albums-set-view("grid");
|
||||
}
|
||||
}
|
||||
ToggleButton {
|
||||
icon: @image-url("../assets/icons/list.svg");
|
||||
active: FavoritesState.albums-view-mode == "list";
|
||||
clicked => {
|
||||
FavoritesState.albums-view-mode = "list";
|
||||
FavoritesActions.albums-set-view("list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1502,6 +1502,7 @@ export global FavoritesActions {
|
||||
callback bulk-action(string /* select-all | remove-selected | clear */);
|
||||
callback album-action(string /* album id */, string /* action */);
|
||||
callback albums-search(string /* query */);
|
||||
callback albums-set-view(string /* grid | list */);
|
||||
callback albums-set-sort(string /* sort key */);
|
||||
callback albums-set-group(string /* off | alpha | artist */);
|
||||
callback albums-shuffle();
|
||||
|
||||
Reference in new issue
Block a user