Initial commit -- Core Cooldown v0.1.0

This commit is contained in:
2026-02-07 01:12:32 +02:00
commit 48cbbfa552
47 changed files with 15106 additions and 0 deletions

133
src/lib/stores/timer.ts Normal file
View File

@@ -0,0 +1,133 @@
import { writable, get } from "svelte/store";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { playSound, playBreakEndSound } from "../utils/sounds";
import { config } from "./config";
export interface TimerSnapshot {
state: "running" | "paused" | "breakActive";
currentView: "dashboard" | "breakScreen" | "settings" | "stats";
timeRemaining: number;
totalDuration: number;
progress: number;
hasHadBreak: boolean;
secondsSinceLastBreak: number;
prebreakWarning: boolean;
snoozesUsed: number;
canSnooze: boolean;
breakTitle: string;
breakMessage: string;
breakProgress: number;
breakTimeRemaining: number;
breakTotalDuration: number;
breakPastHalf: boolean;
settingsModified: boolean;
idlePaused: boolean;
naturalBreakOccurred: boolean;
smartBreaksEnabled: boolean;
smartBreakThreshold: number;
}
const defaultSnapshot: TimerSnapshot = {
state: "paused",
currentView: "dashboard",
timeRemaining: 1500,
totalDuration: 1500,
progress: 1.0,
hasHadBreak: false,
secondsSinceLastBreak: 0,
prebreakWarning: false,
snoozesUsed: 0,
canSnooze: true,
breakTitle: "Rest your eyes",
breakMessage: "Look away from the screen. Stretch and relax.",
breakProgress: 0,
breakTimeRemaining: 0,
breakTotalDuration: 0,
breakPastHalf: false,
settingsModified: false,
idlePaused: false,
naturalBreakOccurred: false,
smartBreaksEnabled: true,
smartBreakThreshold: 300,
};
export const timer = writable<TimerSnapshot>(defaultSnapshot);
// Track the current view separately so UI can switch views optimistically
export const currentView = writable<
"dashboard" | "breakScreen" | "settings" | "stats"
>("dashboard");
let initialized = false;
export async function initTimerStore() {
if (initialized) return;
initialized = true;
// Get initial state
try {
const snapshot = await invoke<TimerSnapshot>("get_timer_state");
timer.set(snapshot);
currentView.set(snapshot.currentView);
} catch (e) {
console.error("Failed to get initial timer state:", e);
}
// Listen for tick events
await listen<TimerSnapshot>("timer-tick", (event) => {
timer.set(event.payload);
// Sync view from backend (backend is authoritative for break transitions)
currentView.set(event.payload.currentView);
});
// Listen for pre-break warning
await listen("prebreak-warning", () => {
const cfg = get(config);
if (cfg.sound_enabled) {
playSound(cfg.sound_preset as any, cfg.sound_volume * 0.6);
}
});
// Listen for break started
await listen("break-started", () => {
currentView.set("breakScreen");
const cfg = get(config);
if (cfg.sound_enabled) {
playSound(cfg.sound_preset as any, cfg.sound_volume);
}
});
// Listen for break ended
await listen("break-ended", () => {
currentView.set("dashboard");
const cfg = get(config);
if (cfg.sound_enabled) {
playBreakEndSound(cfg.sound_preset as any, cfg.sound_volume);
}
});
// Listen for natural break detected
await listen<number>("natural-break-detected", (event) => {
const cfg = get(config);
if (cfg.sound_enabled) {
playSound(cfg.sound_preset as any, cfg.sound_volume * 0.5);
}
});
}
// Helper: format seconds as MM:SS
export function formatTime(secs: number): string {
const m = Math.floor(secs / 60);
const s = secs % 60;
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
}
// Helper: format "X ago" string
export function formatDurationAgo(secs: number): string {
if (secs < 60) return `${secs} sec ago`;
const m = Math.floor(secs / 60);
if (m < 60) return m === 1 ? "1 min ago" : `${m} min ago`;
const h = Math.floor(secs / 3600);
return h === 1 ? "1 hr ago" : `${h} hrs ago`;
}