diff options
Diffstat (limited to 'src-tauri/src/timer.rs')
| -rw-r--r-- | src-tauri/src/timer.rs | 135 |
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); + } + } + }); +} |
