feat(ui): Hide changelog indicator in the settings sidebar if changelog was read

This commit is contained in:
LynithDev 2026-07-16 19:32:35 +02:00
parent 7f8b62eb15
commit a4753fb06b
9 changed files with 78 additions and 6 deletions

View file

@ -17,6 +17,9 @@ pub enum BridgeCommand {
RecordSeenVersion {
version: String,
},
SetSeenChangelogVersion {
version: String,
},
SetOnboardingSeen {
seen: bool,
},

View file

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

View file

@ -38,6 +38,12 @@ impl BridgeDispatch {
});
}
pub fn set_seen_changelog_version(&self, version: impl Into<String>) {
self.send(BridgeCommand::SetSeenChangelogVersion {
version: version.into(),
});
}
pub fn set_onboarding_seen(&self, seen: bool) {
self.send(BridgeCommand::SetOnboardingSeen { seen });
}

View file

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

View file

@ -49,6 +49,10 @@ pub fn changelog_groups(query: &UseQuery<ChangelogQuery>) -> Option<Vec<Changelo
}
}
pub fn latest_changelog_version(query: &UseQuery<ChangelogQuery>) -> Option<String> {
changelog_groups(query).and_then(|groups| groups.first().map(|group| group.version.clone()))
}
pub fn changelog_error(query: &UseQuery<ChangelogQuery>) -> Option<String> {
match &*query.read().state() {
QueryStateData::Settled { res: Err(err), .. } => Some(err.to_string()),

View file

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

View file

@ -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::<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<String>) -> Vec<Element> {
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()

View file

@ -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::<String>);
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(

View file

@ -38,6 +38,7 @@ pub struct LauncherSettings {
pub accepted_tos_version: u32,
pub accepted_privacy_version: u32,
pub seen_versions: Vec<String>,
pub seen_changelog_version: Option<String>,
pub modrinth_api_key: Option<String>,
pub curseforge_api_key: Option<String>,
pub custom_api_endpoint: Option<String>,
@ -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,