refactor(ui): dedupe FormatRemaining between UsagePill and UsageMonitorModal

Both viewmodels carried byte-identical copies of the reset-countdown
formatter. Moved it to UsageTimeFormat.FormatRemaining and pointed
both call sites at it; locale keys and output unchanged.
This commit is contained in:
Mika Kuns
2026-08-24 09:27:15 +02:00
parent 29171b104b
commit e164f1e33f
3 changed files with 24 additions and 25 deletions
@@ -0,0 +1,19 @@
using System;
using ClaudeDo.Ui.Localization;
namespace ClaudeDo.Ui.Services;
/// <summary>Shared "Xh Ym" formatting for usage-reset countdowns (pill tooltip + usage monitor gauges).</summary>
public static class UsageTimeFormat
{
public static string FormatRemaining(DateTimeOffset resetsAt)
{
var remaining = resetsAt - DateTimeOffset.UtcNow;
if (remaining < TimeSpan.Zero) remaining = TimeSpan.Zero;
var hours = (int)remaining.TotalHours;
var minutes = remaining.Minutes;
return hours > 0
? Loc.T("usage.pill.durationHoursMinutes", hours, minutes)
: Loc.T("usage.pill.durationMinutes", minutes);
}
}
@@ -391,8 +391,10 @@ public sealed partial class UsageGaugeRowViewModel : ObservableObject
public bool IsWarnSeverity => !string.Equals(Severity, "normal", StringComparison.OrdinalIgnoreCase); public bool IsWarnSeverity => !string.Equals(Severity, "normal", StringComparison.OrdinalIgnoreCase);
// Includes the absolute HH:mm alongside the countdown, unlike the pill tooltip's shorter
// "resetIn" line (usage.pill.resetIn) — the modal has room for the detail, the tooltip doesn't.
public string ResetText => ResetsAt is { } r public string ResetText => ResetsAt is { } r
? Loc.T("modals.usageMonitor.resetIn", FormatRemaining(r), r.ToLocalTime().ToString("HH:mm")) ? Loc.T("modals.usageMonitor.resetIn", UsageTimeFormat.FormatRemaining(r), r.ToLocalTime().ToString("HH:mm"))
: ""; : "";
/// <summary>Live values from a fresh snapshot, without replacing the row instance mid-view.</summary> /// <summary>Live values from a fresh snapshot, without replacing the row instance mid-view.</summary>
@@ -437,17 +439,6 @@ public sealed partial class UsageGaugeRowViewModel : ObservableObject
return Commit(); return Commit();
} }
private static string FormatRemaining(DateTimeOffset resetsAt)
{
var remaining = resetsAt - DateTimeOffset.UtcNow;
if (remaining < TimeSpan.Zero) remaining = TimeSpan.Zero;
var hours = (int)remaining.TotalHours;
var minutes = remaining.Minutes;
return hours > 0
? Loc.T("usage.pill.durationHoursMinutes", hours, minutes)
: Loc.T("usage.pill.durationMinutes", minutes);
}
} }
/// <summary> /// <summary>
@@ -96,9 +96,9 @@ public sealed partial class UsagePillViewModel : ViewModelBase
var lines = new List<string>(); var lines = new List<string>();
if (s.FiveHourResetsAt is { } fiveReset) if (s.FiveHourResetsAt is { } fiveReset)
lines.Add(Loc.T("usage.pill.resetIn", Loc.T("usage.pill.fiveHourLabel"), FormatRemaining(fiveReset))); lines.Add(Loc.T("usage.pill.resetIn", Loc.T("usage.pill.fiveHourLabel"), UsageTimeFormat.FormatRemaining(fiveReset)));
if (s.SevenDayResetsAt is { } sevenReset) if (s.SevenDayResetsAt is { } sevenReset)
lines.Add(Loc.T("usage.pill.resetIn", Loc.T("usage.pill.sevenDayLabel"), FormatRemaining(sevenReset))); lines.Add(Loc.T("usage.pill.resetIn", Loc.T("usage.pill.sevenDayLabel"), UsageTimeFormat.FormatRemaining(sevenReset)));
if (s.IsGateBlocked && !string.IsNullOrEmpty(s.GateReason)) if (s.IsGateBlocked && !string.IsNullOrEmpty(s.GateReason))
lines.Add(Loc.T("usage.pill.blockedReason", s.GateReason)); lines.Add(Loc.T("usage.pill.blockedReason", s.GateReason));
else if (s.EffectiveSlots < s.ConfiguredSlots) else if (s.EffectiveSlots < s.ConfiguredSlots)
@@ -120,15 +120,4 @@ public sealed partial class UsagePillViewModel : ViewModelBase
"seven_day" => Loc.T("usage.pill.sevenDayLabel"), "seven_day" => Loc.T("usage.pill.sevenDayLabel"),
_ => "", _ => "",
}; };
private static string FormatRemaining(DateTimeOffset resetsAt)
{
var remaining = resetsAt - DateTimeOffset.UtcNow;
if (remaining < TimeSpan.Zero) remaining = TimeSpan.Zero;
var hours = (int)remaining.TotalHours;
var minutes = remaining.Minutes;
return hours > 0
? Loc.T("usage.pill.durationHoursMinutes", hours, minutes)
: Loc.T("usage.pill.durationMinutes", minutes);
}
} }