From 8748ef12110cb1997001ba45b8b435eed16bd1fd Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Mon, 18 May 2026 13:50:11 -0600 Subject: [PATCH] feat(qbz-slint): splash screen during session restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a splash screen (logo, wordmark, spinner) shown while the app boots and restores a saved session. The login form no longer flashes for returning users — the app starts on `splash` and resolves to `shell` (restored) or `login` (no saved session), matching the current QBZ app behavior. cc --- crates/qbz-slint/src/main.rs | 10 ++- crates/qbz-slint/ui/app.slint | 12 ++-- crates/qbz-slint/ui/splash/SplashScreen.slint | 72 +++++++++++++++++++ 3 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 crates/qbz-slint/ui/splash/SplashScreen.slint diff --git a/crates/qbz-slint/src/main.rs b/crates/qbz-slint/src/main.rs index 5ae975a3f..a65e908de 100644 --- a/crates/qbz-slint/src/main.rs +++ b/crates/qbz-slint/src/main.rs @@ -115,8 +115,14 @@ fn main() -> Result<(), Box> { log::info!("[qbz-slint] session restored for user {user_id}"); enter_shell(runtime, weak, image_cache).await; } - Ok(None) => log::info!("[qbz-slint] no saved session — showing login"), - Err(e) => log::error!("[qbz-slint] session restore failed: {e}"), + Ok(None) => { + log::info!("[qbz-slint] no saved session — showing login"); + let _ = weak.upgrade_in_event_loop(|w| w.set_screen(AppScreen::Login)); + } + Err(e) => { + log::error!("[qbz-slint] session restore failed: {e}"); + let _ = weak.upgrade_in_event_loop(|w| w.set_screen(AppScreen::Login)); + } } }); } diff --git a/crates/qbz-slint/ui/app.slint b/crates/qbz-slint/ui/app.slint index 650a91166..28436ba88 100644 --- a/crates/qbz-slint/ui/app.slint +++ b/crates/qbz-slint/ui/app.slint @@ -5,16 +5,16 @@ // (POC ADR, sections 13 and 21). import { Theme } from "foundation/semantic-colors.slint"; +import { SplashScreen } from "splash/SplashScreen.slint"; import { LoginScreen } from "login/LoginScreen.slint"; import { AppShell } from "shell/AppShell.slint"; // Re-export the HomeState global so the Rust layer can populate it. export { HomeState } from "state.slint"; -// Which top-level screen is shown. During M1 the screen switches on a -// login-button click without real authentication; M2 ties the switch -// to a real session. -export enum AppScreen { login, shell } +// Which top-level screen is shown. The app starts on `splash` while it +// restores a saved session, then resolves to `shell` or `login`. +export enum AppScreen { splash, login, shell } export component AppWindow inherits Window { title: "QBZ"; @@ -24,7 +24,7 @@ export component AppWindow inherits Window { min-height: 600px; background: Theme.surface-main; - in-out property screen: AppScreen.login; + in-out property screen: AppScreen.splash; // Forwarded to the Rust layer, which maps them to typed AppCommands. callback sign-in-via-browser(); @@ -32,6 +32,8 @@ export component AppWindow inherits Window { callback start-offline(); callback open-tos(); + if root.screen == AppScreen.splash: SplashScreen { } + if root.screen == AppScreen.login: LoginScreen { sign-in-via-browser => { root.sign-in-via-browser(); diff --git a/crates/qbz-slint/ui/splash/SplashScreen.slint b/crates/qbz-slint/ui/splash/SplashScreen.slint new file mode 100644 index 000000000..01c759be0 --- /dev/null +++ b/crates/qbz-slint/ui/splash/SplashScreen.slint @@ -0,0 +1,72 @@ +// Splash screen — shown while the app boots and tries to restore a +// saved session, so the login form never flashes for returning users. + +import { Theme } from "../foundation/semantic-colors.slint"; +import { Typography } from "../foundation/typography.slint"; +import { Radius } from "../foundation/radius.slint"; +import { Spacing } from "../foundation/spacing.slint"; +import { Spinner } from "std-widgets.slint"; + +export component SplashScreen inherits Rectangle { + background: Theme.surface-main; + + VerticalLayout { + alignment: center; + + HorizontalLayout { + alignment: center; + + Rectangle { + width: 720px; + background: Theme.surface-card; + border-radius: Radius.lg; + drop-shadow-blur: 32px; + drop-shadow-color: Theme.card-shadow; + drop-shadow-offset-y: 8px; + + VerticalLayout { + padding: Spacing.card-padding; + spacing: 0px; + + HorizontalLayout { + alignment: center; + Image { + source: @image-url("../assets/qbz-logo.png"); + width: 140px; + height: 140px; + image-fit: contain; + } + } + Rectangle { height: Spacing.sm; } + Text { + text: @tr("QBZ"); + color: Theme.text-primary; + font-size: Typography.wordmark; + font-weight: Typography.semibold; + letter-spacing: 8px; + horizontal-alignment: center; + } + Rectangle { height: 2px; } + Text { + text: @tr("QOBUZ™ PLAYER"); + color: Theme.text-muted; + font-size: Typography.subtitle; + letter-spacing: 4px; + horizontal-alignment: center; + } + + Rectangle { height: Spacing.xl; } + + HorizontalLayout { + alignment: center; + Spinner { + indeterminate: true; + width: 30px; + height: 30px; + } + } + } + } + } + } +}