feat(slint): Last.fm and ListenBrainz scrobblers wired to player and settings

Re-implements the parked scrobblers-wip stash intent on the current tree
(the stash no longer applied; it remains untouched in the stash list) with
three corrections: the Last.fm offline queue uses the SHARED per-user
offline_settings.db scrobble_queue (byte-identical SQL to Tauri - queues
flush from either frontend) instead of a private table; the ListenBrainz
cache opens Tauri's per-user path so LB creds and listen_queue are truly
shared (a Tauri LB sign-in pre-seeds Slint); and the queue flush is driven
by the offline engine's online edge (plus initial flush), not login-only.

New frontend-agnostic qbz_app::settings::scrobblers store (master enable,
Last.fm session/username, LB token/username; tests) and the shared
scrobble-queue API on OfflineModeStore. Player wiring hooks the de-duped
track-change edge inside the QConnect remote gate: now-playing fires on
change (skipped offline), the scrobble arms at min(50%, 240s) with a
generation counter cancelling stale timers, all sources scrobble (Tauri
parity incl. the #360 version-enriched title). Offline or call failure
queues; Last.fm flush honors the 50-batch and 14-day cutoff.

Settings gains an Integrations section (Plex-section idiom): Last.fm
browser-auth connect/finish/disconnect, ListenBrainz token field +
validate, per-service enable toggles. Known gap documented: Last.fm creds
cannot be shared with Tauri (webview localStorage); LB creds and both
queues are shared.

cc
This commit is contained in:
vicrodh committed 2026-06-10 02:52:17 -06:00
1 parent 81587f4d17
commit 952846b6a0
12 files changed
+1945 -2

No files matched your search

+46
View File
@@ -854,6 +854,52 @@ export global PlexAuthActions {
callback load(); // panel init: seed from store + warm cache
}
// ============================ Scrobblers ==================================
// Settings > Integrations — source-agnostic Last.fm + ListenBrainz scrobbling.
// State mirrors `scrobbler_settings.db`; actions delegate to the Rust
// `scrobble` controller (auth flows + the now-playing/scrobble fire live
// there). ADR-008: no pills.
export global ScrobbleState {
// --- Master section toggles -------------------------------------------
in-out property <bool> enabled: false; // master toggle (default OFF)
in-out property <bool> ui-collapsed: false; // collapse chevron
// --- Last.fm ----------------------------------------------------------
in-out property <bool> lastfm-enabled: false; // per-service scrobble flag
in property <bool> lastfm-authed: false; // session key present
in property <string> lastfm-username: ""; // "Signed in as ..."
in property <string> lastfm-auth-url: ""; // non-empty after connect
in property <bool> lastfm-busy: false; // get_token / get_session in flight
// --- ListenBrainz -----------------------------------------------------
in-out property <bool> listenbrainz-enabled: false;
in property <bool> listenbrainz-authed: false;
in property <string> listenbrainz-username: "";
in-out property <string> listenbrainz-token-input: ""; // token field buffer
in property <bool> listenbrainz-busy: false; // set_token in flight
// --- Shared status line (0 none, 1 info, 2 ok, 3 error) ---------------
in property <string> status-text: "";
in property <int> status-kind: 0;
}
export global ScrobbleActions {
callback load(); // panel init: seed from store
callback enable-toggle(bool);
callback collapse-toggle(bool);
// Last.fm.
callback lastfm-enable-toggle(bool);
callback lastfm-connect(); // get_token + open browser
callback lastfm-open-auth-url(); // re-open authorize URL
callback lastfm-confirm(); // get_session after approve
callback lastfm-disconnect();
// ListenBrainz.
callback listenbrainz-enable-toggle(bool);
callback listenbrainz-set-token(string);
callback listenbrainz-disconnect();
}
// ============================ Tag editor ==================================
// Local-album metadata editor (Settings-independent modal launched from the
// local album detail). Sidecar (default) + opt-in direct file-write. Remote