From a4753fb06ba36c3f9995c0ba6e7d0b664d3e1b1e Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 16 Jul 2026 19:32:35 +0200 Subject: [PATCH] feat(ui): Hide changelog indicator in the settings sidebar if changelog was read --- packages/oneclient_app/src/bridge/commands.rs | 3 +++ packages/oneclient_app/src/bridge/runtime.rs | 17 +++++++++++++ packages/oneclient_app/src/hooks/dispatch.rs | 6 +++++ packages/oneclient_app/src/hooks/mod.rs | 3 ++- .../src/hooks/queries/changelog.rs | 4 ++++ .../oneclient_app/src/hooks/queries/mod.rs | 4 +++- .../src/layout/settings_shell.rs | 24 ++++++++++++++++--- .../src/view/app/settings/changelog.rs | 21 +++++++++++++++- .../oneclient_core/src/settings/launcher.rs | 2 ++ 9 files changed, 78 insertions(+), 6 deletions(-) diff --git a/packages/oneclient_app/src/bridge/commands.rs b/packages/oneclient_app/src/bridge/commands.rs index 1be97d5a..de7972a9 100644 --- a/packages/oneclient_app/src/bridge/commands.rs +++ b/packages/oneclient_app/src/bridge/commands.rs @@ -17,6 +17,9 @@ pub enum BridgeCommand { RecordSeenVersion { version: String, }, + SetSeenChangelogVersion { + version: String, + }, SetOnboardingSeen { seen: bool, }, diff --git a/packages/oneclient_app/src/bridge/runtime.rs b/packages/oneclient_app/src/bridge/runtime.rs index d15afc11..89843f32 100644 --- a/packages/oneclient_app/src/bridge/runtime.rs +++ b/packages/oneclient_app/src/bridge/runtime.rs @@ -315,6 +315,23 @@ impl CoreBridgeRuntime { } })?; } + BridgeCommand::SetSeenChangelogVersion { version } => { + let mut changed = false; + mutate_settings(snapshots, |settings| { + if settings.seen_changelog_version.as_deref() != Some(version.as_str()) { + settings.seen_changelog_version = Some(version); + changed = true; + } + })?; + + if changed { + let state = LauncherState::get()?; + let snapshot = state.settings.read().clone(); + if let Err(err) = save_settings(&snapshot).await { + snapshots.settings.error = Some(err.to_string()); + } + } + } BridgeCommand::SetOnboardingSeen { seen } => { mutate_settings(snapshots, |settings| { settings.seen_onboarding = seen; diff --git a/packages/oneclient_app/src/hooks/dispatch.rs b/packages/oneclient_app/src/hooks/dispatch.rs index 62bee53d..4841e0a5 100644 --- a/packages/oneclient_app/src/hooks/dispatch.rs +++ b/packages/oneclient_app/src/hooks/dispatch.rs @@ -38,6 +38,12 @@ impl BridgeDispatch { }); } + pub fn set_seen_changelog_version(&self, version: impl Into) { + self.send(BridgeCommand::SetSeenChangelogVersion { + version: version.into(), + }); + } + pub fn set_onboarding_seen(&self, seen: bool) { self.send(BridgeCommand::SetOnboardingSeen { seen }); } diff --git a/packages/oneclient_app/src/hooks/mod.rs b/packages/oneclient_app/src/hooks/mod.rs index 9bc877bf..bf20a3e3 100644 --- a/packages/oneclient_app/src/hooks/mod.rs +++ b/packages/oneclient_app/src/hooks/mod.rs @@ -25,7 +25,8 @@ pub use queries::{ category_list, changelog_error, changelog_groups, changelog_is_loading, cluster_content_items, content_type_for_slug, has_migration_data, invalidate_cluster_queries, invalidate_java_queries, invalidate_logs_queries, invalidate_profile_queries, invalidate_screenshots_queries, - java_runtimes, loader_versions, login_code_already_handled, migration_detections, + java_runtimes, latest_changelog_version, loader_versions, login_code_already_handled, + migration_detections, mutation_error, mutation_is_pending, onboarding_bundles_items, package_meta_batch, pick_version_metadata, project_detail, provider_versions, reset_login_code_dedup, search_items, search_pending, search_total, terms_document, terms_error, terms_is_loading, try_account, diff --git a/packages/oneclient_app/src/hooks/queries/changelog.rs b/packages/oneclient_app/src/hooks/queries/changelog.rs index c9ad5da8..0bf215c6 100644 --- a/packages/oneclient_app/src/hooks/queries/changelog.rs +++ b/packages/oneclient_app/src/hooks/queries/changelog.rs @@ -49,6 +49,10 @@ pub fn changelog_groups(query: &UseQuery) -> Option) -> Option { + changelog_groups(query).and_then(|groups| groups.first().map(|group| group.version.clone())) +} + pub fn changelog_error(query: &UseQuery) -> Option { match &*query.read().state() { QueryStateData::Settled { res: Err(err), .. } => Some(err.to_string()), diff --git a/packages/oneclient_app/src/hooks/queries/mod.rs b/packages/oneclient_app/src/hooks/queries/mod.rs index f70c14e2..0601d592 100644 --- a/packages/oneclient_app/src/hooks/queries/mod.rs +++ b/packages/oneclient_app/src/hooks/queries/mod.rs @@ -37,7 +37,9 @@ pub use bundles::{ onboarding_bundles_items, use_bundle_overrides, use_bundle_updates, use_bundles_with_status, use_onboarding_bundles, }; -pub use changelog::{changelog_error, changelog_groups, changelog_is_loading, use_changelog}; +pub use changelog::{ + changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version, use_changelog, +}; pub use cluster_content::{cluster_content_items, use_cluster_content}; pub use clusters::use_clusters; pub use image::{CachedImageQuery, use_cached_image}; diff --git a/packages/oneclient_app/src/layout/settings_shell.rs b/packages/oneclient_app/src/layout/settings_shell.rs index 8fe9d55e..015705a0 100644 --- a/packages/oneclient_app/src/layout/settings_shell.rs +++ b/packages/oneclient_app/src/layout/settings_shell.rs @@ -11,6 +11,7 @@ use sysinfo::RefreshKind; use sysinfo::System; use crate::components::{Icon, IconType, ScrollArea, ScrollAreaCtx, TextInput}; +use crate::hooks::{latest_changelog_version, use_changelog, use_settings_snapshot}; use crate::routes::Route; use crate::theme::colors; use crate::use_dispatch; @@ -360,6 +361,8 @@ impl Component for SettingsShell { let route = use_route::(); let active = route_tab(&route); + let changelog_unread = has_unread_changelog(); + let search = use_state(String::new); let query = search.read().trim().to_string(); let scroll = use_scroll_controller(ScrollConfig::default); @@ -400,7 +403,7 @@ impl Component for SettingsShell { .overflow(Overflow::Clip) .padding(Gaps::new(0., 40., 40., 32.)) .spacing(40.) - .child(sidebar(active)) + .child(sidebar(active, changelog_unread)) .child( rect() .vertical() @@ -574,7 +577,20 @@ fn search_results(query: String, mut search: State) -> Vec { out } -fn sidebar(active: SettingsTab) -> impl IntoElement { +/// True when the newest changelog entry is newer than the one the user last read. +/// While the changelog is still loading or failed to load we show no dot rather +/// than guessing. +fn has_unread_changelog() -> bool { + let settings = use_settings_snapshot().settings; + let query = use_changelog(); + + match latest_changelog_version(&query) { + Some(latest) => settings.seen_changelog_version.as_deref() != Some(latest.as_str()), + None => false, + } +} + +fn sidebar(active: SettingsTab, changelog_unread: bool) -> impl IntoElement { rect() .vertical() .width(Size::px(SIDEBAR_WIDTH_PX)) @@ -606,6 +622,7 @@ fn sidebar(active: SettingsTab) -> impl IntoElement { SidebarItem { tab: *tab, active: *tab == active, + has_dot: matches!(tab, SettingsTab::Changelog) && changelog_unread, } .into_element() })) @@ -713,6 +730,7 @@ impl Component for SidebarInfo { struct SidebarItem { tab: SettingsTab, active: bool, + has_dot: bool, } impl Component for SidebarItem { @@ -734,7 +752,7 @@ impl Component for SidebarItem { Color::TRANSPARENT }; - let has_dot = matches!(tab, SettingsTab::Changelog); + let has_dot = self.has_dot; let mut el = rect() .horizontal() diff --git a/packages/oneclient_app/src/view/app/settings/changelog.rs b/packages/oneclient_app/src/view/app/settings/changelog.rs index 791bf819..adc96f94 100644 --- a/packages/oneclient_app/src/view/app/settings/changelog.rs +++ b/packages/oneclient_app/src/view/app/settings/changelog.rs @@ -2,7 +2,10 @@ use freya::prelude::*; use super::settings_page; use crate::components::{Icon, IconType}; -use crate::hooks::{changelog_error, changelog_groups, changelog_is_loading, use_changelog}; +use crate::hooks::{ + changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version, + use_changelog, use_dispatch, use_settings_snapshot, +}; use crate::theme::colors; #[derive(PartialEq)] @@ -11,8 +14,24 @@ pub struct SettingsChangelog; impl Component for SettingsChangelog { fn render(&self) -> impl IntoElement { let query = use_changelog(); + let dispatch = use_dispatch(); + let settings = use_settings_snapshot().settings; + let mut marked = use_state(|| None::); let installed_version = env!("CARGO_PKG_VERSION").to_string(); + // Opening this page counts as reading the changelog, so clear the sidebar + // dot once the newest entry is known. `marked` keeps us from re-sending the + // command while the settings snapshot catches up. + if let Some(latest) = latest_changelog_version(&query) { + let already_marked = marked.peek().as_deref() == Some(latest.as_str()); + let already_seen = settings.seen_changelog_version.as_deref() == Some(latest.as_str()); + + if !already_marked && !already_seen { + marked.set(Some(latest.clone())); + dispatch.set_seen_changelog_version(latest); + } + } + if changelog_is_loading(&query) { return settings_page() .child( diff --git a/packages/oneclient_core/src/settings/launcher.rs b/packages/oneclient_core/src/settings/launcher.rs index 9de8c728..0f56279c 100644 --- a/packages/oneclient_core/src/settings/launcher.rs +++ b/packages/oneclient_core/src/settings/launcher.rs @@ -38,6 +38,7 @@ pub struct LauncherSettings { pub accepted_tos_version: u32, pub accepted_privacy_version: u32, pub seen_versions: Vec, + pub seen_changelog_version: Option, pub modrinth_api_key: Option, pub curseforge_api_key: Option, pub custom_api_endpoint: Option, @@ -72,6 +73,7 @@ impl Default for LauncherSettings { accepted_tos_version: 0, accepted_privacy_version: 0, seen_versions: Vec::new(), + seen_changelog_version: None, modrinth_api_key: None, curseforge_api_key: None, custom_api_endpoint: None,