+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
-
-
-
-
diff --git a/dash-frontend/src/tab/donate.rs b/dash-frontend/src/tab/donate.rs
index 4ab8c0b2..e934fd8b 100644
--- a/dash-frontend/src/tab/donate.rs
+++ b/dash-frontend/src/tab/donate.rs
@@ -4,19 +4,23 @@ use wgui::{
assets::AssetPath,
components::button::ComponentButton,
globals::WguiGlobals,
- layout::WidgetID,
- parser::{Fetchable, ParseDocumentParams, ParserState},
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
+ taffy::{self, style_helpers::length},
task::Tasks,
+ widget::div::WidgetDiv,
};
use crate::{
frontend::{Frontend, FrontendTask},
tab::{Tab, TabType},
+ util::cached_fetcher,
};
-#[derive(Clone)]
#[allow(clippy::enum_variant_names)]
-enum Task {}
+enum Task {
+ SetSupporters(cached_fetcher::Supporters),
+}
pub struct TabDonate
{
#[allow(dead_code)]
@@ -24,6 +28,8 @@ pub struct TabDonate {
marker: PhantomData,
#[allow(dead_code)]
tasks: Tasks,
+
+ id_current_supporters: WidgetID,
}
impl Tab for TabDonate {
@@ -31,10 +37,14 @@ impl Tab for TabDonate {
TabType::Donate
}
- fn update(&mut self, _frontend: &mut Frontend, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
- // for task in self.tasks.drain() {
- // match task {}
- // }
+ fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
+ while !self.tasks.is_empty() {
+ for task in self.tasks.drain() {
+ match task {
+ Task::SetSupporters(supporters) => self.set_supporters(&mut frontend.layout, supporters)?,
+ }
+ }
+ }
Ok(())
}
@@ -48,9 +58,76 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
}
}
+async fn request_supporters(tasks: Tasks) {
+ if let Some(supporters) = cached_fetcher::request_supporters().await {
+ tasks.push(Task::SetSupporters(supporters))
+ }
+}
+
+fn tier_pretty_print(tier: &str) -> String {
+ format!("{} Tier", wlx_common::locale::capitalize_string(tier))
+}
+
+fn tier_color(tier: &str) -> &'static str {
+ match tier {
+ "platinum" => "#aaffff",
+ "gold" => "#ffffaa",
+ "silver" => "#cccccc",
+ "bronze" => "#ffaa66",
+ _ => "on_background",
+ }
+}
+
impl TabDonate {
+ fn set_supporters(&mut self, layout: &mut Layout, supporters: cached_fetcher::Supporters) -> anyhow::Result<()> {
+ let globals = layout.state.globals.clone();
+ layout.remove_children(self.id_current_supporters);
+
+ let mut current_tier = "";
+ let mut tier_parent = WidgetID::default();
+
+ for supporter in &supporters.supporters {
+ if supporter.tier != current_tier {
+ current_tier = &supporter.tier;
+
+ let mut params = TemplateParams::new();
+ params.insert_str("text", tier_pretty_print(&supporter.tier));
+ params.insert("color", tier_color(&supporter.tier));
+ self.state.realize_template(
+ &doc_params(&globals),
+ "TierCell",
+ layout,
+ self.id_current_supporters,
+ params,
+ )?;
+
+ tier_parent = layout
+ .add_child(
+ self.id_current_supporters,
+ WidgetDiv::create(),
+ taffy::Style {
+ gap: length(8.0_f32),
+ flex_wrap: taffy::FlexWrap::Wrap,
+ ..Default::default()
+ },
+ )?
+ .0
+ .id;
+ }
+
+ let mut params = TemplateParams::new();
+ params.insert("username", &supporter.username);
+ self
+ .state
+ .realize_template(&doc_params(&globals), "SupporterCell", layout, tier_parent, params)?;
+ }
+
+ Ok(())
+ }
+
pub fn new(frontend: &mut Frontend, parent_id: WidgetID, _data: &mut T) -> anyhow::Result {
let state = wgui::parser::parse_from_assets(&doc_params(&frontend.globals), &mut frontend.layout, parent_id)?;
+ let id_current_supporters = state.get_widget_id("current_supporters")?;
frontend.tasks.handle_button(
&state.fetch_component_as::("btn_donate")?,
@@ -59,10 +136,13 @@ impl TabDonate {
let tasks = Tasks::::new();
+ frontend.executor.spawn(request_supporters(tasks.clone())).detach();
+
Ok(Self {
state,
marker: PhantomData,
tasks,
+ id_current_supporters,
})
}
}
diff --git a/dash-frontend/src/tab/home.rs b/dash-frontend/src/tab/home.rs
index b6b6a03e..e5f78bf9 100644
--- a/dash-frontend/src/tab/home.rs
+++ b/dash-frontend/src/tab/home.rs
@@ -30,12 +30,7 @@ impl Tab for TabHome {
}
fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, config: &GeneralConfig) {
- let mut username = various::get_username();
- // first character as uppercase
- if let Some(first) = username.chars().next() {
- let first = first.to_uppercase().to_string();
- username.replace_range(0..1, &first);
- }
+ let username = wlx_common::locale::capitalize_string(&various::get_username());
let translated = if !config.hide_username {
common.i18n().translate_and_replace("HELLO_USER", ("{USER}", &username))
diff --git a/dash-frontend/src/tab/mod.rs b/dash-frontend/src/tab/mod.rs
index 881084de..af9c6b1e 100644
--- a/dash-frontend/src/tab/mod.rs
+++ b/dash-frontend/src/tab/mod.rs
@@ -24,7 +24,7 @@ pub enum TabType {
impl TabType {
pub fn get_preferred_padding(&self) -> f32 {
match self {
- TabType::Welcome | TabType::Donate => 0.0,
+ TabType::Welcome => 0.0,
_ => 16.0,
}
}
diff --git a/dash-frontend/src/util/cached_fetcher.rs b/dash-frontend/src/util/cached_fetcher.rs
index 54d55d31..e04fcba8 100644
--- a/dash-frontend/src/util/cached_fetcher.rs
+++ b/dash-frontend/src/util/cached_fetcher.rs
@@ -1,14 +1,73 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+
use crate::util::{networking::http_client, steam_utils::AppID};
use anyhow::Context;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use wlx_common::cache_dir;
+fn get_unix_timestamp() -> u64 {
+ SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Supporter {
+ pub username: String,
+ pub date: String,
+ pub tier: String,
+ pub contribution_count: u32,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Supporters {
+ pub time_window_days: u32,
+ pub supporters: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+struct SupportersFile {
+ fetch_timestamp: u64,
+ supporters: Option, // empty in case of server/network failure
+}
+
+pub async fn request_supporters() -> Option {
+ let cache_file_path = "supporters_v1.json";
+ const CACHE_DURATION_SECS: u64 = 7200;
+ let current_timestamp = get_unix_timestamp();
+
+ if let Some(data) = cache_dir::get_data(cache_file_path).await
+ && let Ok(string) = str::from_utf8(&data)
+ && let Ok(file) = serde_json::from_str::(string)
+ && file.fetch_timestamp + CACHE_DURATION_SECS > current_timestamp
+ {
+ return file.supporters;
+ }
+
+ // perform request
+ let mut supporters_file = SupportersFile {
+ fetch_timestamp: current_timestamp,
+ supporters: None,
+ };
+
+ let url = "https://wayvr.org/files/supporters_v1.json";
+ if let Ok(res) = http_client::get_simple(url).await
+ && let Ok(supporters) = res.into_json::()
+ {
+ supporters_file.supporters = Some(supporters);
+ }
+
+ let json = serde_json::to_string_pretty(&supporters_file).unwrap() /* safe */;
+
+ cache_dir::set_data(cache_file_path, json.as_bytes()).await.ok()?;
+
+ supporters_file.supporters
+}
+
pub struct CoverArt {
// can be empty in case if data couldn't be fetched (use a fallback image then)
pub compressed_image_data: Vec,
}
-pub async fn request_image(app_id: AppID) -> anyhow::Result {
+pub async fn request_cover_art(app_id: AppID) -> anyhow::Result {
let cache_file_path = format!("cover_arts/{}.bin", app_id);
// check if file already exists in cache directory
diff --git a/dash-frontend/src/util/openxr_bindings.rs b/dash-frontend/src/util/openxr_bindings.rs
index bb4e420e..ec14b45c 100644
--- a/dash-frontend/src/util/openxr_bindings.rs
+++ b/dash-frontend/src/util/openxr_bindings.rs
@@ -46,14 +46,7 @@ impl BindingsDropdown for XrInputSubpathKind {
self
.get_str("Translation")
.map(Translation::from_translation_key)
- .unwrap_or_else(|| {
- let mut chars = self.as_ref().chars();
- let capitalized = match chars.next() {
- None => String::new(),
- Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
- };
- Translation::from_raw_text(&capitalized)
- })
+ .unwrap_or_else(|| Translation::from_raw_text(&wlx_common::locale::capitalize_string(self.as_ref())))
}
fn action_str(&self, action: &str, side: XrInputSide) -> Rc {
let value = self.as_ref();
diff --git a/dash-frontend/src/views/game_cover.rs b/dash-frontend/src/views/game_cover.rs
index f9a68793..7ab5659e 100644
--- a/dash-frontend/src/views/game_cover.rs
+++ b/dash-frontend/src/views/game_cover.rs
@@ -62,7 +62,7 @@ const GAME_COVER_SIZE_Y: f32 = 210.0;
impl View {
async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box) {
- let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
+ let cover_art = match cached_fetcher::request_cover_art(manifest.app_id.clone()).await {
Ok(cover_art) => cover_art,
Err(e) => {
log::error!("request_cover_image failed: {:?}", e);
@@ -75,7 +75,7 @@ impl View {
fn mount_image(&self, layout: &mut Layout, glyph: &CustomGlyphData) -> anyhow::Result<()> {
let image = WidgetImage::create(WidgetImageParams {
- round: WLength::Units(10.0),
+ round: WLength::Units(6.0),
glyph_data: Some(glyph.clone()),
..Default::default()
});
@@ -173,7 +173,7 @@ impl View {
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0).into()),
border_color: Some(BORDER_COLOR_DEFAULT.into()),
hover_border_color: Some(BORDER_COLOR_HOVERED.into()),
- round: WLength::Units(12.0),
+ round: WLength::Units(8.0),
border: 2.0,
tooltip: Some(TooltipInfo {
side: TooltipSide::Bottom,
@@ -209,11 +209,11 @@ impl View {
},
)?;
- let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
+ let rect_gradient = |color: drawing::Color, color2: drawing::Color, round: f32| {
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
color: color.into(),
color2: color2.into(),
- round: WLength::Units(12.0),
+ round: WLength::Units(round),
gradient: GradientMode::Vertical,
..Default::default()
})
@@ -234,7 +234,8 @@ impl View {
widget_button.id,
rect_gradient(
drawing::Color::new(1.0, 1.0, 1.0, 0.2),
- drawing::Color::new(1.0, 1.0, 1.0, 0.02),
+ drawing::Color::new(1.0, 1.0, 1.0, 0.01),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.05),
)?;
@@ -248,6 +249,7 @@ impl View {
rect_gradient(
drawing::Color::new(1.0, 1.0, 1.0, 0.15),
drawing::Color::new(1.0, 1.0, 1.0, 0.0),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.5),
)?;
@@ -258,6 +260,7 @@ impl View {
rect_gradient(
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::END, 0.5),
)?;
@@ -268,6 +271,7 @@ impl View {
rect_gradient(
drawing::Color::new(0.0, 0.0, 0.0, 0.1),
drawing::Color::new(0.0, 0.0, 0.0, 0.9),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::END, 0.05),
)?;
diff --git a/wgui/src/palette.rs b/wgui/src/palette.rs
index 2bc054a6..43f1558e 100644
--- a/wgui/src/palette.rs
+++ b/wgui/src/palette.rs
@@ -128,7 +128,7 @@ static DEFAULT: &WguiColorPalette = &WguiColorPalette {
hex("#ffebf5"), // OnDanger
hex("#002e43"), // Background
hex("#e4f5f6"), // OnBackground
- hex("#0c5170"), // OackgroundVariant
+ hex("#0c5170"), // OnBackgroundVariant
hex("#b5cacc"), // OnBackgroundVariant
hex("#00131c"), // BackgroundContrast
hex("#e4edf6"), // OnBackgroundContrast
diff --git a/wlx-common/src/locale.rs b/wlx-common/src/locale.rs
index 337b6a39..d87e87bc 100644
--- a/wlx-common/src/locale.rs
+++ b/wlx-common/src/locale.rs
@@ -89,3 +89,11 @@ impl WayVRLangProvider {
Self::default()
}
}
+
+pub fn capitalize_string(s: &str) -> String {
+ let mut chars = s.chars();
+ match chars.next() {
+ None => String::new(),
+ Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
+ }
+}