From 950a38aad8724d260ec78bac4e0a84f8090deb1e Mon Sep 17 00:00:00 2001 From: SirBlobby Date: Sat, 31 Oct 2026 18:41:16 -0400 Subject: [PATCH] Add battery details popover --- src/lib/components/shell/Battery.svelte | 44 +++- .../components/shell/BatteryPopover.svelte | 197 ++++++++++++++++++ src/lib/desktop/battery.ts | 56 ++++- 3 files changed, 282 insertions(+), 15 deletions(-) create mode 100644 src/lib/components/shell/BatteryPopover.svelte diff --git a/src/lib/components/shell/Battery.svelte b/src/lib/components/shell/Battery.svelte index 097c6d0..ff581a6 100644 --- a/src/lib/components/shell/Battery.svelte +++ b/src/lib/components/shell/Battery.svelte @@ -1,11 +1,13 @@ {#if battery} - - - {battery.level}% - +
+ + + {#if open} + (open = false)} /> + {/if} +
{/if} diff --git a/src/lib/desktop/battery.ts b/src/lib/desktop/battery.ts index fb30391..b383cd1 100644 --- a/src/lib/desktop/battery.ts +++ b/src/lib/desktop/battery.ts @@ -1,11 +1,18 @@ -const hour = 3600 * 1000; +const minute = 60 * 1000; +const hour = 60 * minute; +const dayDuration = 24 * hour; const dischargeDuration = 5 * hour; const chargeDuration = 1 * hour; const cycleDuration = dischargeDuration + chargeDuration; +const cycleCountStart = Date.UTC(2025, 10, 21); + +export const batteryCapacityWh = 42; export interface BatteryState { level: number; charging: boolean; + minutesRemaining: number; + chargeCycles: number; } function easternWallClockMs(date: Date): number { @@ -24,12 +31,24 @@ function easternWallClockMs(date: Date): number { } export function batteryAt(date: Date): BatteryState { - const position = easternWallClockMs(date) % cycleDuration; + const wallClock = easternWallClockMs(date); + const position = wallClock % cycleDuration; + const chargeCycles = Math.floor((wallClock - cycleCountStart) / dayDuration); if (position < dischargeDuration) { - return { level: Math.round(100 - (position / dischargeDuration) * 100), charging: false }; + return { + level: Math.round(100 - (position / dischargeDuration) * 100), + charging: false, + minutesRemaining: Math.round((dischargeDuration - position) / minute), + chargeCycles + }; } - return { level: Math.round(((position - dischargeDuration) / chargeDuration) * 100), charging: true }; + return { + level: Math.round(((position - dischargeDuration) / chargeDuration) * 100), + charging: true, + minutesRemaining: Math.round((cycleDuration - position) / minute), + chargeCycles + }; } export function batteryIcon({ level, charging }: BatteryState): string { @@ -41,3 +60,32 @@ export function batteryIcon({ level, charging }: BatteryState): string { if (level > 10) return 'mdi:battery-20'; return 'mdi:battery-alert'; } + +export function isBatteryFull({ level }: BatteryState): boolean { + return level >= 100; +} + +export function isBatteryLow({ level, charging }: BatteryState): boolean { + return level < 20 && !charging; +} + +export function batteryStatusLabel(battery: BatteryState): string { + if (isBatteryFull(battery)) return 'Fully charged'; + return battery.charging ? 'Charging' : 'Discharging'; +} + +export function batteryHealth({ chargeCycles }: BatteryState): number { + return Math.max(80, 100 - Math.round(chargeCycles / 40)); +} + +export function powerRateLabel({ charging }: BatteryState): string { + const durationInHours = (charging ? chargeDuration : dischargeDuration) / hour; + const watts = (batteryCapacityWh / durationInHours).toFixed(1); + return charging ? `+${watts}W` : `-${watts}W`; +} + +export function formatDuration(totalMinutes: number): string { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; +} \ No newline at end of file