From 5cac70b0266051da8e3176aa240be9262d18c49e Mon Sep 17 00:00:00 2001 From: Maciek <108873945+rozwader@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:16:01 +0200 Subject: [PATCH] feat: added progress bar on first launch screen; changed progress bar to reusable component (there were 4 duplicates) (#740) --- packages/oneclient_app/src/components/mod.rs | 2 ++ .../src/components/notifications.rs | 20 +++++-------- .../src/components/progress_track.rs | 20 +++++++++++++ .../oneclient_app/src/components/toasts.rs | 22 ++++++--------- .../view/onboarding/downloading/progress.rs | 18 +----------- packages/oneclient_app/src/view/startup.rs | 28 +++++++++++++++---- 6 files changed, 60 insertions(+), 50 deletions(-) create mode 100644 packages/oneclient_app/src/components/progress_track.rs diff --git a/packages/oneclient_app/src/components/mod.rs b/packages/oneclient_app/src/components/mod.rs index d9d29b74..cf8a40f1 100644 --- a/packages/oneclient_app/src/components/mod.rs +++ b/packages/oneclient_app/src/components/mod.rs @@ -24,6 +24,7 @@ mod package_row; mod package_update_popup; mod pagination; mod player_model; +mod progress_track; mod recents_row; mod screenshot_viewer; mod scrollview; @@ -66,6 +67,7 @@ pub use package_row::{CardLayout, PackageEntry, PackageRow, provider_badge}; pub use package_update_popup::PackageUpdatePopup; pub use pagination::Pagination; pub use player_model::PlayerModel; +pub use progress_track::progress_track; pub use recents_row::RecentsRow; pub use screenshot_viewer::ScreenshotViewer; pub(crate) use scrollview::corrected_scroll; diff --git a/packages/oneclient_app/src/components/notifications.rs b/packages/oneclient_app/src/components/notifications.rs index 77125538..7e56682e 100644 --- a/packages/oneclient_app/src/components/notifications.rs +++ b/packages/oneclient_app/src/components/notifications.rs @@ -6,7 +6,7 @@ use oneclient_events::Level; use crate::{ ui::{divider, relative_time}, - components::{Button, ButtonVariant, Icon, IconType, OverlayPopup, ScrollArea}, + components::{Button, ButtonVariant, Icon, IconType, OverlayPopup, ScrollArea, progress_track}, hooks::{use_dispatch, use_notifications_snapshot}, notifications::{InboxEntry, NotificationActionKind}, theme::colors, @@ -382,18 +382,12 @@ fn row_body(entry: &InboxEntry, dispatch: crate::Actions) -> impl IntoElement { fn row_extra(entry: &InboxEntry, dispatch: crate::Actions) -> impl IntoElement { if let Some((current, total)) = entry.progress.filter(|_| entry.is_loading) { let frac = (current as f32 / total.max(1) as f32).clamp(0.0, 1.0); - let bar = rect() - .width(Size::fill()) - .height(Size::px(6.)) - .corner_radius(CornerRadius::new_all(50.)) - .background(colors::brand().with_a(128)) - .child( - rect() - .width(Size::percent(frac * 100.0)) - .height(Size::px(6.)) - .corner_radius(CornerRadius::new_all(50.)) - .background(colors::brand()), - ); + let bar = progress_track( + frac * 100.0, + 6., + colors::brand(), + colors::brand().with_a(128), + ); return rect() .vertical() diff --git a/packages/oneclient_app/src/components/progress_track.rs b/packages/oneclient_app/src/components/progress_track.rs new file mode 100644 index 00000000..742596d0 --- /dev/null +++ b/packages/oneclient_app/src/components/progress_track.rs @@ -0,0 +1,20 @@ +use freya::prelude::*; + +/// The app's one progress meter: a pill-shaped track with a filled portion. +/// +/// `pct` is 0-100 and is clamped here, so callers can hand over a raw ratio +/// without guarding against a denominator that grew mid-flight. +pub fn progress_track(pct: f32, height: f32, fill: Color, bg: Color) -> Rect { + rect() + .width(Size::fill()) + .height(Size::px(height)) + .corner_radius(CornerRadius::new_all(height / 2.)) + .background(bg) + .child( + rect() + .width(Size::percent(pct.clamp(0.0, 100.0))) + .height(Size::fill()) + .corner_radius(CornerRadius::new_all(height / 2.)) + .background(fill), + ) +} diff --git a/packages/oneclient_app/src/components/toasts.rs b/packages/oneclient_app/src/components/toasts.rs index 2c5718e6..3ede3ce2 100644 --- a/packages/oneclient_app/src/components/toasts.rs +++ b/packages/oneclient_app/src/components/toasts.rs @@ -11,7 +11,7 @@ use oneclient_events::Level; use crate::{ ui::{divider}, - components::{Button, ButtonSize, Icon, IconType}, + components::{Button, ButtonSize, Icon, IconType, progress_track}, hooks::{use_dispatch, use_notifications_snapshot}, notifications::{InboxEntry, NotificationActionKind}, theme::colors, @@ -440,19 +440,13 @@ fn progress_card(entry: &InboxEntry) -> Rect { .color(colors::fg_secondary()), ) .child( - rect() - .width(Size::fill()) - .height(Size::px(6.)) - .margin(Gaps::new(8., 0., 0., 0.)) - .corner_radius(CornerRadius::new_all(50.)) - .background(colors::brand().with_a(128)) - .child( - rect() - .width(Size::percent(frac * 100.0)) - .height(Size::px(6.)) - .corner_radius(CornerRadius::new_all(50.)) - .background(colors::brand()), - ), + progress_track( + frac * 100.0, + 6., + colors::brand(), + colors::brand().with_a(128), + ) + .margin(Gaps::new(8., 0., 0., 0.)), ), ), ) diff --git a/packages/oneclient_app/src/view/onboarding/downloading/progress.rs b/packages/oneclient_app/src/view/onboarding/downloading/progress.rs index c3becf38..344f1601 100644 --- a/packages/oneclient_app/src/view/onboarding/downloading/progress.rs +++ b/packages/oneclient_app/src/view/onboarding/downloading/progress.rs @@ -1,6 +1,6 @@ use super::*; -use crate::components::Button; +use crate::components::{Button, progress_track}; use crate::theme::colors; use crate::ui::border_all_color; use crate::utils::{format_duration_hms, format_size}; @@ -389,19 +389,3 @@ impl Component for ActivityIndicator { ) } } - -fn progress_track(pct: f32, height: f32, fill: Color, bg: Color) -> impl IntoElement { - rect() - .width(Size::fill()) - .height(Size::px(height)) - .corner_radius(CornerRadius::new_all(height / 2.)) - .background(bg) - .child( - rect() - .width(Size::percent(pct.clamp(0.0, 100.0))) - .height(Size::fill()) - .corner_radius(CornerRadius::new_all(height / 2.)) - .background(fill), - ) - .into_element() -} diff --git a/packages/oneclient_app/src/view/startup.rs b/packages/oneclient_app/src/view/startup.rs index a7e1edb8..dd4c13c8 100644 --- a/packages/oneclient_app/src/view/startup.rs +++ b/packages/oneclient_app/src/view/startup.rs @@ -8,9 +8,13 @@ use crate::hooks::{ use_splash, use_terms, }; use crate::hooks::use_dispatch; -use crate::components::Button; +use crate::components::{Button, progress_track}; use crate::theme::colors; +/// Startup is a sequence of boolean gates, not measurable work, so the bar steps +/// once per gate the launcher clears instead of tracking bytes +const STARTUP_STEPS: u8 = 3; + #[derive(PartialEq)] struct RecoveryActions { snapshots: usize, @@ -124,11 +128,13 @@ impl Component for Startup { let active = notifications.inbox.iter().find(|entry| entry.is_loading); - let (message, detail): (String, Option) = + let (message, detail, step): (String, Option, u8) = if let Some(err) = launcher.error.as_deref() { + let step = if launcher.ready { 2 } else { 1 }; ( "Couldn't start OneClient".to_string(), Some(err.to_string()), + step, ) } else if let Some(entry) = active { let detail = entry @@ -137,11 +143,11 @@ impl Component for Startup { .find(|t| t.total == 0 || t.current < t.total) .map(|t| t.label.clone()) .or_else(|| entry.progress.map(|(c, t)| format!("{c} / {t}"))); - (entry.title.clone(), detail) + (entry.title.clone(), detail, 3) } else if launcher.ready { - ("Fetching versions and bundles...".to_string(), None) + ("Fetching versions and bundles...".to_string(), None, 2) } else { - ("Starting OneClient...".to_string(), None) + ("Starting OneClient...".to_string(), None, 1) }; let is_error = launcher.error.is_some(); @@ -156,6 +162,15 @@ impl Component for Startup { colors::brand() }; + let filled = f32::from(step) / f32::from(STARTUP_STEPS + 1) * 100.; + + let progress = rect().width(Size::px(288.)).child(progress_track( + filled, + 4., + dot_color, + colors::fg_primary().with_a(30), + )); + let loading_row = rect() .horizontal() .cross_align(Alignment::Center) @@ -192,7 +207,8 @@ impl Component for Startup { .color(colors::fg_primary().with_a(140)), ) .child(rect().height(Size::px(14.))) - .child(loading_row); + .child(loading_row) + .child(progress); if let Some(detail) = detail { content = content.child(