refactor: thread detailed flag through countdown formatter

Adds a `detailed: bool` parameter to `format_line`, `format_countdown`,
`format_countdown_from_secs`, `time_until_display_change`, and
`time_until_display_change_from_secs`. When `true`, sub-units are
surfaced (Xh Ym for hours, Xd Yh for days) and the display-change
tick fires on minute/hour boundaries to match.

All callers pass `false`, so behavior is unchanged. The toggle that
flips this is added in a later commit.
This commit is contained in:
Carlos Miguel C. Resurreccion 2026-05-15 15:09:01 +08:00
parent 71ddfe3ccf
commit 8f8d5d072d
2 changed files with 50 additions and 20 deletions

View File

@ -1021,9 +1021,9 @@ fn is_leap(y: u64) -> bool {
}
/// Format a usage section as "X% · Yh" style text
pub fn format_line(section: &UsageSection, strings: Strings) -> String {
pub fn format_line(section: &UsageSection, strings: Strings, detailed: bool) -> String {
let pct = format!("{:.0}%", section.percentage);
let cd = format_countdown(section.resets_at, strings);
let cd = format_countdown(section.resets_at, strings, detailed);
if cd.is_empty() {
pct
} else {
@ -1031,7 +1031,7 @@ pub fn format_line(section: &UsageSection, strings: Strings) -> String {
}
}
fn format_countdown(resets_at: Option<SystemTime>, strings: Strings) -> String {
fn format_countdown(resets_at: Option<SystemTime>, strings: Strings, detailed: bool) -> String {
let reset = match resets_at {
Some(t) => t,
None => return String::new(),
@ -1042,25 +1042,47 @@ fn format_countdown(resets_at: Option<SystemTime>, strings: Strings) -> String {
Err(_) => return strings.now.to_string(),
};
format_countdown_from_secs(remaining.as_secs(), strings)
format_countdown_from_secs(remaining.as_secs(), strings, detailed)
}
/// Calculate how long until the display text would change
pub fn time_until_display_change(resets_at: Option<SystemTime>) -> Option<Duration> {
pub fn time_until_display_change(
resets_at: Option<SystemTime>,
detailed: bool,
) -> Option<Duration> {
let reset = resets_at?;
let remaining = reset.duration_since(SystemTime::now()).ok()?;
Some(time_until_display_change_from_secs(remaining.as_secs()))
Some(time_until_display_change_from_secs(
remaining.as_secs(),
detailed,
))
}
fn format_countdown_from_secs(total_secs: u64, strings: Strings) -> String {
fn format_countdown_from_secs(total_secs: u64, strings: Strings, detailed: bool) -> String {
let total_mins = total_secs / 60;
let total_hours = total_secs / 3600;
let total_days = total_secs / 86400;
if total_days >= 1 {
format!("{total_days}{}", strings.day_suffix)
if detailed {
let hours = total_hours % 24;
format!(
"{total_days}{} {hours}{}",
strings.day_suffix, strings.hour_suffix
)
} else {
format!("{total_days}{}", strings.day_suffix)
}
} else if total_hours >= 1 {
format!("{total_hours}{}", strings.hour_suffix)
if detailed {
let mins = total_mins % 60;
format!(
"{total_hours}{} {mins}{}",
strings.hour_suffix, strings.minute_suffix
)
} else {
format!("{total_hours}{}", strings.hour_suffix)
}
} else if total_mins >= 1 {
format!("{total_mins}{}", strings.minute_suffix)
} else {
@ -1068,15 +1090,23 @@ fn format_countdown_from_secs(total_secs: u64, strings: Strings) -> String {
}
}
fn time_until_display_change_from_secs(total_secs: u64) -> Duration {
fn time_until_display_change_from_secs(total_secs: u64, detailed: bool) -> Duration {
let total_mins = total_secs / 60;
let total_hours = total_secs / 3600;
let total_days = total_secs / 86400;
let current_bucket_start = if total_days >= 1 {
total_days * 86400
if detailed {
total_hours * 3600
} else {
total_days * 86400
}
} else if total_hours >= 1 {
total_hours * 3600
if detailed {
total_mins * 60
} else {
total_hours * 3600
}
} else if total_mins >= 1 {
total_mins * 60
} else {

View File

@ -420,16 +420,16 @@ fn refresh_usage_texts(state: &mut AppState) {
};
if let Some(claude_code) = data.claude_code.as_ref() {
state.session_text = poller::format_line(&claude_code.session, strings);
state.weekly_text = poller::format_line(&claude_code.weekly, strings);
state.session_text = poller::format_line(&claude_code.session, strings, false);
state.weekly_text = poller::format_line(&claude_code.weekly, strings, false);
} else if state.show_claude_code {
state.session_text = "!".to_string();
state.weekly_text = "!".to_string();
}
if let Some(codex) = data.codex.as_ref() {
state.codex_session_text = poller::format_line(&codex.session, strings);
state.codex_weekly_text = poller::format_line(&codex.weekly, strings);
state.codex_session_text = poller::format_line(&codex.session, strings, false);
state.codex_weekly_text = poller::format_line(&codex.weekly, strings, false);
} else if state.show_codex {
state.codex_session_text = "!".to_string();
state.codex_weekly_text = "!".to_string();
@ -1634,16 +1634,16 @@ fn schedule_countdown_timer() {
let delays = [
data.claude_code
.as_ref()
.and_then(|usage| poller::time_until_display_change(usage.session.resets_at)),
.and_then(|usage| poller::time_until_display_change(usage.session.resets_at, false)),
data.claude_code
.as_ref()
.and_then(|usage| poller::time_until_display_change(usage.weekly.resets_at)),
.and_then(|usage| poller::time_until_display_change(usage.weekly.resets_at, false)),
data.codex
.as_ref()
.and_then(|usage| poller::time_until_display_change(usage.session.resets_at)),
.and_then(|usage| poller::time_until_display_change(usage.session.resets_at, false)),
data.codex
.as_ref()
.and_then(|usage| poller::time_until_display_change(usage.weekly.resets_at)),
.and_then(|usage| poller::time_until_display_change(usage.weekly.resets_at, false)),
];
let min_delay = delays.into_iter().flatten().min();