feat: added progress bar on first launch screen; changed progress bar to reusable component (there were 4 duplicates) (#740)

This commit is contained in:
Maciek 2026-08-25 15:16:01 +02:00 • committed by GitHub
parent e659ee122a
commit 5cac70b026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 60 additions and 50 deletions

View file

@ -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;

View file

@ -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()

View file

@ -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),
)
}

View file

@ -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.)),
),
),
)

View file

@ -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()
}

View file

@ -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<String>) =
let (message, detail, step): (String, Option<String>, 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(