diff options
Diffstat (limited to 'src-tauri/src/lib.rs')
| -rw-r--r-- | src-tauri/src/lib.rs | 292 |
1 files changed, 288 insertions, 4 deletions
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4a277ef..3ca8d99 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,14 +1,298 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +mod state; +mod storage; +mod timer; + +use std::sync::{Arc, Mutex}; +use chrono::Utc; +use tauri::{AppHandle, Manager, State}; +use uuid::Uuid; + +use state::{AppDataWrapper, TimerPhase, TimerState, TimerStateWrapper}; +use storage::{Settings, Task}; + +// ── Helper ────────────────────────────────────────────────────────────────── + +fn app_data_dir(app: &AppHandle) -> std::path::PathBuf { + app.path().app_data_dir().expect("Failed to resolve app data dir") +} + +// ── Timer commands ────────────────────────────────────────────────────────── + +#[tauri::command] +fn get_timer_status(timer: State<'_, TimerStateWrapper>) -> serde_json::Value { + let ts = timer.0.lock().unwrap(); + serde_json::json!({ + "phase": ts.phase, + "remaining_secs": ts.remaining_secs, + "total_secs": ts.total_secs, + "running": ts.running, + "session_count": ts.session_count, + "current_task_id": ts.current_task_id, + }) +} + +#[tauri::command] +fn start_timer(timer: State<'_, TimerStateWrapper>) { + let mut ts = timer.0.lock().unwrap(); + ts.running = true; +} + #[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) +fn pause_timer(timer: State<'_, TimerStateWrapper>) { + let mut ts = timer.0.lock().unwrap(); + ts.running = false; } +#[tauri::command] +fn reset_timer(timer: State<'_, TimerStateWrapper>, data: State<'_, AppDataWrapper>) { + let mut ts = timer.0.lock().unwrap(); + let total = { + let app_data = data.data.lock().unwrap(); + match ts.phase { + TimerPhase::Work => app_data.settings.work_duration_secs, + TimerPhase::ShortBreak => app_data.settings.short_break_secs, + TimerPhase::LongBreak => app_data.settings.long_break_secs, + } + }; + ts.remaining_secs = total; + ts.total_secs = total; + ts.running = false; +} + +#[tauri::command] +fn skip_phase( + timer: State<'_, TimerStateWrapper>, + data: State<'_, AppDataWrapper>, + app: AppHandle, +) { + use tauri::Emitter; + let mut ts = timer.0.lock().unwrap(); + let app_data = data.data.lock().unwrap(); + let s = &app_data.settings; + + match ts.phase { + TimerPhase::Work => { + ts.session_count += 1; + let (next_phase, next_secs) = + 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_secs; + ts.remaining_secs = next_secs; + } + TimerPhase::ShortBreak | TimerPhase::LongBreak => { + ts.phase = TimerPhase::Work; + ts.total_secs = s.work_duration_secs; + ts.remaining_secs = s.work_duration_secs; + } + } + + ts.running = false; + + let _ = app.emit( + "timer-phase-changed", + timer::PhaseChangedPayload { + phase: ts.phase, + session_count: ts.session_count, + }, + ); +} + +#[tauri::command] +fn set_current_task( + task_id: Option<String>, + timer: State<'_, TimerStateWrapper>, + data: State<'_, AppDataWrapper>, + app: AppHandle, +) -> Result<(), String> { + { + let mut app_data = data.data.lock().unwrap(); + // Validate task exists if Some + if let Some(ref tid) = task_id { + if !app_data.tasks.iter().any(|t| &t.id == tid) { + return Err(format!("Task {} not found", tid)); + } + } + app_data.current_task_id = task_id.clone(); + storage::save(&app_data_dir(&app), &app_data)?; + } + { + let mut ts = timer.0.lock().unwrap(); + ts.current_task_id = task_id; + } + Ok(()) +} + +// ── Settings commands ─────────────────────────────────────────────────────── + +#[tauri::command] +fn get_settings(data: State<'_, AppDataWrapper>) -> Settings { + data.data.lock().unwrap().settings.clone() +} + +#[tauri::command] +fn update_settings( + settings: Settings, + data: State<'_, AppDataWrapper>, + timer: State<'_, TimerStateWrapper>, + app: AppHandle, +) -> Result<(), String> { + { + let mut app_data = data.data.lock().unwrap(); + app_data.settings = settings.clone(); + storage::save(&app_data_dir(&app), &app_data)?; + } + // Restart timer at new work duration + { + let mut ts = timer.0.lock().unwrap(); + ts.phase = TimerPhase::Work; + ts.total_secs = settings.work_duration_secs; + ts.remaining_secs = settings.work_duration_secs; + ts.running = false; + ts.session_count = 0; + } + Ok(()) +} + +// ── Task commands ─────────────────────────────────────────────────────────── + +#[tauri::command] +fn get_tasks(data: State<'_, AppDataWrapper>) -> Vec<Task> { + data.data.lock().unwrap().tasks.clone() +} + +#[tauri::command] +fn add_task( + name: String, + total_sessions: u32, + data: State<'_, AppDataWrapper>, + app: AppHandle, +) -> Result<Task, String> { + let task = Task { + id: Uuid::new_v4().to_string(), + name, + total_sessions, + remaining_sessions: total_sessions, + completed: false, + created_at: Utc::now().to_rfc3339(), + }; + { + let mut app_data = data.data.lock().unwrap(); + app_data.tasks.push(task.clone()); + storage::save(&app_data_dir(&app), &app_data)?; + } + Ok(task) +} + +#[tauri::command] +fn update_task( + id: String, + remaining_sessions: Option<u32>, + completed: Option<bool>, + data: State<'_, AppDataWrapper>, + app: AppHandle, +) -> Result<(), String> { + let mut app_data = data.data.lock().unwrap(); + let task = app_data + .tasks + .iter_mut() + .find(|t| t.id == id) + .ok_or_else(|| format!("Task {} not found", id))?; + if let Some(r) = remaining_sessions { + task.remaining_sessions = r; + } + if let Some(c) = completed { + task.completed = c; + } + storage::save(&app_data_dir(&app), &app_data)?; + Ok(()) +} + +#[tauri::command] +fn delete_task( + id: String, + data: State<'_, AppDataWrapper>, + timer: State<'_, TimerStateWrapper>, + app: AppHandle, +) -> Result<(), String> { + { + let mut app_data = data.data.lock().unwrap(); + let before = app_data.tasks.len(); + app_data.tasks.retain(|t| t.id != id); + if app_data.tasks.len() == before { + return Err(format!("Task {} not found", id)); + } + // Clear current_task_id if it was this task + if app_data.current_task_id.as_deref() == Some(&id) { + app_data.current_task_id = None; + } + storage::save(&app_data_dir(&app), &app_data)?; + } + { + let mut ts = timer.0.lock().unwrap(); + if ts.current_task_id.as_deref() == Some(&id) { + ts.current_task_id = None; + } + } + Ok(()) +} + +// ── App entry point ───────────────────────────────────────────────────────── + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) + .setup(|app| { + let data_dir = app.handle().path().app_data_dir() + .expect("Failed to resolve app data dir"); + + // Load persisted data + let app_data = storage::load(&data_dir); + let work_secs = app_data.settings.work_duration_secs; + let current_task_id = app_data.current_task_id.clone(); + + let data_arc = Arc::new(Mutex::new(app_data)); + let timer_arc = Arc::new(Mutex::new({ + let mut ts = TimerState::new(work_secs); + ts.current_task_id = current_task_id; + ts + })); + + // Register managed state + app.manage(TimerStateWrapper(Arc::clone(&timer_arc))); + app.manage(AppDataWrapper { + data: Arc::clone(&data_arc), + data_dir: data_dir.clone(), + }); + + // Spawn background timer thread + timer::spawn_timer_thread( + app.handle().clone(), + Arc::clone(&timer_arc), + Arc::clone(&data_arc), + data_dir, + ); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + get_timer_status, + start_timer, + pause_timer, + reset_timer, + skip_phase, + set_current_task, + get_settings, + update_settings, + get_tasks, + add_task, + update_task, + delete_task, + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } |
