summaryrefslogtreecommitdiff
path: root/src-tauri/src/timer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/timer.rs')
-rw-r--r--src-tauri/src/timer.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/src-tauri/src/timer.rs b/src-tauri/src/timer.rs
new file mode 100644
index 0000000..7e26ad8
--- /dev/null
+++ b/src-tauri/src/timer.rs
@@ -0,0 +1,135 @@
+use std::sync::{Arc, Mutex};
+use std::thread;
+use std::time::Duration;
+use serde::Serialize;
+use tauri::{AppHandle, Emitter};
+
+use crate::state::{TimerPhase, TimerState};
+use crate::storage::{self, AppData};
+
+// ── Event payloads ──────────────────────────────────────────────────────────
+
+#[derive(Clone, Serialize)]
+pub struct TickPayload {
+ pub phase: TimerPhase,
+ pub remaining_secs: u64,
+ pub total_secs: u64,
+ pub session_count: u32,
+ pub current_task_id: Option<String>,
+}
+
+#[derive(Clone, Serialize)]
+pub struct PhaseChangedPayload {
+ pub phase: TimerPhase,
+ pub session_count: u32,
+}
+
+#[derive(Clone, Serialize)]
+pub struct CompletedPayload {
+ pub task_id: Option<String>,
+}
+
+// ── Timer thread ────────────────────────────────────────────────────────────
+
+/// Spawns the background timer thread. The thread runs for the lifetime of
+/// the application; it sleeps while `running == false`.
+pub fn spawn_timer_thread(
+ app_handle: AppHandle,
+ timer_arc: Arc<Mutex<TimerState>>,
+ data_arc: Arc<Mutex<AppData>>,
+ data_dir: std::path::PathBuf,
+) {
+ thread::spawn(move || {
+ loop {
+ thread::sleep(Duration::from_secs(1));
+
+ let mut ts = timer_arc.lock().unwrap();
+
+ if !ts.running {
+ continue;
+ }
+
+ // Decrement
+ if ts.remaining_secs > 0 {
+ ts.remaining_secs -= 1;
+ }
+
+ // Emit tick
+ let tick = TickPayload {
+ phase: ts.phase,
+ remaining_secs: ts.remaining_secs,
+ total_secs: ts.total_secs,
+ session_count: ts.session_count,
+ current_task_id: ts.current_task_id.clone(),
+ };
+ let _ = app_handle.emit("timer-tick", tick);
+
+ // Check if phase has reached zero
+ if ts.remaining_secs == 0 {
+ ts.running = false;
+
+ match ts.phase {
+ TimerPhase::Work => {
+ // Emit completion before transitioning
+ let completed = CompletedPayload {
+ task_id: ts.current_task_id.clone(),
+ };
+ let _ = app_handle.emit("timer-completed", completed);
+
+ // Decrement task remaining sessions
+ let task_id = ts.current_task_id.clone();
+ {
+ let mut data = data_arc.lock().unwrap();
+ if let Some(ref tid) = task_id {
+ if let Some(task) = data.tasks.iter_mut().find(|t| &t.id == tid) {
+ if task.remaining_sessions > 0 {
+ task.remaining_sessions -= 1;
+ }
+ if task.remaining_sessions == 0 {
+ task.completed = true;
+ }
+ }
+ }
+ let _ = storage::save(&data_dir, &data);
+ }
+
+ ts.session_count += 1;
+
+ // Determine next phase
+ let (next_phase, next_duration) = {
+ let data = data_arc.lock().unwrap();
+ let s = &data.settings;
+ if ts.session_count % s.sessions_before_long_break == 0 {
+ (TimerPhase::LongBreak, s.long_break_secs)
+ } else {
+ (TimerPhase::ShortBreak, s.short_break_secs)
+ }
+ };
+
+ ts.phase = next_phase;
+ ts.total_secs = next_duration;
+ ts.remaining_secs = next_duration;
+ }
+
+ TimerPhase::ShortBreak | TimerPhase::LongBreak => {
+ // Transition back to work
+ let work_secs = {
+ let data = data_arc.lock().unwrap();
+ data.settings.work_duration_secs
+ };
+ ts.phase = TimerPhase::Work;
+ ts.total_secs = work_secs;
+ ts.remaining_secs = work_secs;
+ }
+ }
+
+ // Emit phase-changed after state is updated
+ let phase_changed = PhaseChangedPayload {
+ phase: ts.phase,
+ session_count: ts.session_count,
+ };
+ let _ = app_handle.emit("timer-phase-changed", phase_changed);
+ }
+ }
+ });
+}