Desktop: background store reads from the tray (no document.hidden skip, backoff), notifications by kind (rejected, approved, live, halted, crash, reviews) with switches, quiet hours and mute, click-through on next show; tray menu with apps and their states, read now, mute, updates, settings, tooltip summary and attention dot; tray_update command; tests

This commit is contained in:
CRTkafa 2026-10-22 22:24:05 +08:00
parent 5d8a0f5e9a
commit 03ef999dac
27 changed files with 1050 additions and 55 deletions

View file

@ -269,9 +269,39 @@ pub struct CacheSettings {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct NotifySettings {
/// Other release state changes (submitted, in review).
pub review_state: bool,
/// A version the store rejected.
pub rejected: bool,
/// A version that passed review (ready for release).
pub approved: bool,
/// A version that went live or started rolling out.
pub live: bool,
pub low_reviews: bool,
/// A Play crash / ANR rate over the threshold, or a halted rollout.
pub crash: bool,
pub quiet_hours: QuietHours,
}
/// No notification between `from` and `to` (HH:MM, local time; the window may cross midnight).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct QuietHours {
pub enabled: bool,
pub from: String,
pub to: String,
}
impl Default for QuietHours {
fn default() -> QuietHours {
QuietHours { enabled: false, from: "23:00".into(), to: "08:00".into() }
}
}
/// "HH:MM" with a valid hour and minute.
pub fn valid_hhmm(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 5 && b[2] == b':' && b.iter().enumerate().all(|(i, c)| i == 2 || c.is_ascii_digit()) && s[..2].parse::<u8>().is_ok_and(|h| h < 24) && s[3..].parse::<u8>().is_ok_and(|m| m < 60)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -300,7 +330,7 @@ impl Default for CacheSettings {
impl Default for NotifySettings {
fn default() -> NotifySettings {
NotifySettings { review_state: true, low_reviews: true, crash: true }
NotifySettings { review_state: true, rejected: true, approved: true, live: true, low_reviews: true, crash: true, quiet_hours: QuietHours::default() }
}
}
@ -327,6 +357,9 @@ pub fn check_settings(s: &Settings) -> Result<(), String> {
if !KEEP_DAYS.contains(&s.cache.keep_days) {
return Err("keep saved reads: 0 (always), 7, 30, 90 or 365 days".into());
}
if !valid_hhmm(&s.notify.quiet_hours.from) || !valid_hhmm(&s.notify.quiet_hours.to) {
return Err("quiet hours: times as HH:MM (00:00 to 23:59)".into());
}
if !["stable", "beta"].contains(&s.channel.as_str()) {
return Err("channel: stable or beta".into());
}