S3 of the Lyrics epic: the qbz-lyrics engine reaches the Slint shell — per-user cache binding, the track-edge fetch rider, the LyricsState global, and the real sidebar body replacing the placeholder. - Glue (src/lyrics.rs, offline_mode/fav_cache template): process-global LyricsService over the core client lock (read at call time, never cached); init_for_user on ALL session activation paths (browser login, saved-session restore, offline entry) binding the SAME per-user lyrics/lyrics.db Tauri uses; teardown on logout. - Fetch rider on the NOTIFY_LAST_TRACK dedupe edge in refresh_now_playing_meta, next to scrobble/notify — prefetch on every track change regardless of panel visibility (Tauri parity, lyricsStore.ts:545-565) and deliberately NO skip-if-remote (Q7: lyrics follow the QConnect peer). Track->null resets the state. Request carries source kind (qobuz_download counts as Qobuz, real catalog id) + the offline engine verdict; raw title to the engine (F4). - Stale guard (F2): responses echo the request identity and are dropped unless they still match the latest requested track. Duplicate-fetch guard mirrors Tauri's lastFetchedTrackId (skip only while loaded). Parsed doc (native wsync words) held Rust-side for the S4 engine. - LyricsState global: line model (text, time-ms, end-ms, has-words), status (idle/loading/ready/no-lyrics/error/offline), synced, provider + attribution label, header meta, active-index placeholder (-1), and panel-opened (fetch-on-open via conditional-mount init). - LyricsLinesView (shared, miniplayer-ready): 280px-adapted typography per the owner Q3 spec - 13/15px reduced scale, 0.2px letter-spacing, 6px side padding, full-width wrapped text; dimming ladder on the INNER Text (never a root opacity binding); centering spacers from the Flickable's own height; plain mode = static top-aligned at 0.85; fallback-provider attribution line (Qobuz first-party = none). - Sidebar 1:1 header (mic tile 36px, LYRICS overline 11px/1.5px per the alignment standard, title-artist meta, sliders trigger for the S5 flyout, close kept) + state bodies incl. the typed NotAvailableOffline as a translated string (F3). All new strings @tr()-wrapped. Sync engine (position, active line, auto-center, karaoke fill) is S4. cc
110 lines
4.1 KiB
TOML
110 lines
4.1 KiB
TOML
[package]
|
|
name = "qbz-slint"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
repository.workspace = true
|
|
description = "Slint functional MVP shell for QBZ (private branch only)"
|
|
|
|
[[bin]]
|
|
name = "qbz-slint"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
# femtovg renderer, pinned >=1.16 for the femtovg text-rendering fixes
|
|
# (slint-ui/slint #5177 pixel-grid alignment + #11335 0.5px tearing /
|
|
# inconsistent glyph spacing). Skia never received that treatment.
|
|
slint = { version = "1.16", default-features = false, features = [
|
|
"std",
|
|
"backend-winit",
|
|
"renderer-femtovg",
|
|
"renderer-software",
|
|
"accessibility",
|
|
"compat-1-2",
|
|
] }
|
|
# Direct access to the winit backend for the system tray: WinitWindowAccessor
|
|
# (hide/show the OS window without quitting the event loop) and, later, the
|
|
# CustomApplicationHandler (tray event pump + close-to-tray intercept). MUST be
|
|
# pinned to the exact version Slint resolves (1.16.1) — i-slint-backend-winit
|
|
# does not follow semver, and a mismatch makes with_winit_window() return None.
|
|
i-slint-backend-winit = "=1.16.1"
|
|
log = { workspace = true }
|
|
env_logger = "0.11"
|
|
tokio = { workspace = true }
|
|
async-trait = { workspace = true }
|
|
reqwest = { workspace = true }
|
|
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
|
|
urlencoding = "2"
|
|
open = "5"
|
|
arboard = "3"
|
|
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
dirs = "5"
|
|
chrono = { workspace = true, features = ["unstable-locales"] }
|
|
qbz-app = { path = "../qbz-app" }
|
|
qbz-media-controls = { path = "../qbz-media-controls" }
|
|
qbz-audio = { path = "../qbz-audio" }
|
|
qbz-core = { path = "../qbz-core" }
|
|
qbz-player = { path = "../qbz-player" }
|
|
qconnect-app = { path = "../qconnect-app" }
|
|
qconnect-transport-ws = { path = "../qconnect-transport-ws" }
|
|
futures-util = { workspace = true }
|
|
uuid = { version = "1", features = ["v4"] }
|
|
qbz-library = { path = "../qbz-library" }
|
|
qbz-plex = { path = "../qbz-plex" }
|
|
qbz-offline-cache = { path = "../qbz-offline-cache" }
|
|
qbz-models = { path = "../qbz-models" }
|
|
qbz-mixtape = { path = "../qbz-mixtape" }
|
|
qbz-qobuz = { path = "../qbz-qobuz" }
|
|
qbz-lyrics = { path = "../qbz-lyrics" }
|
|
qbz-integrations = { path = "../qbz-integrations" }
|
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
|
# DJ-mix sampler thread RNG (rand::rng()), confined to a sync scope — the
|
|
# same crate qbz-mixtape::shuffle is generic over (spec 40 §6).
|
|
rand = { workspace = true }
|
|
qbz-cache = { path = "../qbz-cache" }
|
|
qbz-credentials = { path = "../qbz-credentials" }
|
|
|
|
# Linux system tray via StatusNotifierItem. Same crate + rationale as the
|
|
# Tauri build (src-tauri/Cargo.toml): libayatana-appindicator never dispatches
|
|
# primary-activate (left-click), so we use ksni's pure-Rust SNI which exposes
|
|
# Activate / SecondaryActivate / Scroll. macOS/Windows use `tray-icon` instead
|
|
# (added with the CustomApplicationHandler slice).
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
ksni = { version = "0.3", features = ["blocking"] }
|
|
|
|
# macOS menu-bar tray (NSStatusItem), hand-rolled directly on objc2 0.5 /
|
|
# app-kit 0.2 — the SAME objc2 generation winit 0.30 uses — so the NSMenu lives
|
|
# in winit's NSApplication runtime and `[NSApp sendAction:]` dispatches the menu
|
|
# item target-action. A second `muda` (from tray-icon) registers a duplicate
|
|
# `MudaMenuItem` objc class and either silently fails to dispatch (objc2 0.6) or
|
|
# panics at startup (objc2 0.5), so tray-icon/muda is dropped on macOS. Windows
|
|
# has no tray for now (untested/unshipped).
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
objc2 = "0.5"
|
|
objc2-foundation = { version = "0.2", default-features = false, features = [
|
|
"std",
|
|
"NSData",
|
|
"NSString",
|
|
] }
|
|
objc2-app-kit = { version = "0.2", default-features = false, features = [
|
|
"std",
|
|
"NSApplication",
|
|
"NSResponder",
|
|
"NSRunningApplication",
|
|
"NSStatusBar",
|
|
"NSStatusItem",
|
|
"NSStatusBarButton",
|
|
"NSMenu",
|
|
"NSMenuItem",
|
|
"NSImage",
|
|
"NSButton",
|
|
"NSControl",
|
|
"NSView",
|
|
"NSEvent",
|
|
] }
|
|
|
|
[build-dependencies]
|
|
slint-build = "1.16"
|