OneLauncher-Cracked/packages/oneclient_app/src/view/onboarding/bundles/row.rs
Maciek b3b436bc3e
feat: added support for github hosted bundle mods (#883)
* feat: added support for github hosted bundle mods

* fix: keep external bundle mods when their manifest entry gains an id

* fix: match a pending external bundle update to its installed jar

* chore: parse the bundle file host with url instead of by hand

* fix: namespace external bundle file ids under ext:

* chore: rename the Browser tab to Online and list unbundled GitHub mods there

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:30:50 +02:00

173 lines
5.6 KiB
Rust

use freya::prelude::*;
use oneclient_content::packages::ProviderId;
use crate::components::{Icon, IconType, github_badge, provider_badge};
use crate::hooks::{loaded_image, use_cached_image};
use crate::theme::colors;
use crate::ui::{ImageFallbackExt, border_all_color};
use crate::utils::format_size;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const CARD_NAME: Color = Color::from_rgb(213, 219, 255);
pub(super) const CARD_GRID_H: f32 = 148.;
pub(super) const GRID_GAP: f32 = 10.;
#[derive(PartialEq)]
pub(super) struct OnboardingModCard {
pub(super) provider: ProviderId,
pub(super) github_hosted: bool,
pub(super) name: String,
pub(super) author: String,
pub(super) description: String,
pub(super) icon_url: Option<String>,
pub(super) size: u64,
pub(super) enabled: bool,
pub(super) on_toggle: EventHandler<()>,
}
impl Component for OnboardingModCard {
fn render(&self) -> impl IntoElement {
let icon_query = use_cached_image(self.icon_url.clone(), 128);
let loaded = loaded_image(self.icon_url.as_deref(), &icon_query);
let icon = match loaded {
Some((url, bytes)) => ImageViewer::new((url, bytes))
.width(Size::px(44.))
.height(Size::px(44.))
.aspect_ratio(AspectRatio::Min)
.corner_radius(CornerRadius::new_all(8.))
.fallback(icon_box(self.placeholder_icon()))
.into_element(),
None => icon_box(self.placeholder_icon()).into_element(),
};
let (bg, border, alpha) = if self.enabled {
(colors::brand().with_a(38), colors::brand(), 255)
} else {
(CARD_BG, colors::component_border(), 140)
};
let on_toggle = self.on_toggle.clone();
let size = self.size;
let header = rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.spacing(10.)
.content(Content::Flex)
.child(icon)
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(3.)
.child(
label()
.text(self.name.clone())
.font_size(14.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.width(Size::fill())
.color(CARD_NAME.with_a(alpha)),
)
.maybe(!self.author.is_empty(), |el| {
el.child(
label()
.text(format!("by {}", self.author))
.font_size(10.)
.max_lines(1)
.width(Size::fill())
.color(colors::fg_secondary().with_a(alpha)),
)
})
.child(if self.github_hosted {
github_badge()
} else {
provider_badge(self.provider)
}),
);
let description = (!self.description.is_empty()).then(|| {
label()
.text(self.description.clone())
.font_size(11.)
.max_lines(3)
.width(Size::fill())
.color(colors::fg_secondary().with_a(alpha))
.into_element()
});
rect()
.vertical()
.width(Size::fill())
.height(Size::fill())
.spacing(8.)
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(8.))
.background(bg)
.border(border_all_color(1.5, border))
.content(Content::Flex)
.a11y_role(AccessibilityRole::Button)
.cursor(CursorIcon::Pointer)
.on_press(move |_| on_toggle.call(()))
.child(header)
.child(
rect()
.vertical()
.width(Size::fill())
.height(Size::flex(1.0))
.maybe_child(description),
)
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.maybe_child((size > 0).then(|| {
label()
.text(format_size(size))
.font_size(11.)
.color(colors::fg_secondary().with_a(alpha))
.into_element()
})),
)
}
}
impl OnboardingModCard {
fn placeholder_icon(&self) -> IconType {
if self.github_hosted {
IconType::Github
} else {
self.provider.into()
}
}
}
fn icon_box(icon: IconType) -> impl IntoElement {
rect()
.center()
.width(Size::px(44.))
.height(Size::px(44.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.child(Icon::new(icon).size(20.).color(colors::fg_secondary()))
.into_element()
}
pub(super) fn empty_hint(text: &str) -> impl IntoElement {
rect()
.width(Size::fill())
.padding(Gaps::new_all(24.))
.center()
.child(
label()
.text(text.to_string())
.font_size(13.)
.color(colors::fg_secondary()),
)
.into_element()
}