diff options
| -rw-r--r-- | src-tauri/src/lib.rs | 40 | ||||
| -rw-r--r-- | src/App.tsx | 62 | ||||
| -rw-r--r-- | src/components/AmbientControl.tsx | 9 | ||||
| -rw-r--r-- | src/components/TimerView.tsx | 13 |
4 files changed, 82 insertions, 42 deletions
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index aaa9cd2..ae8ad13 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -55,6 +55,10 @@ fn sync_audio_ducking(audio: &AudioState, phase: TimerPhase) { } } +fn clear_session_count_only(timer: &mut TimerState) { + timer.session_count = 0; +} + // ── Timer commands ────────────────────────────────────────────────────────── #[tauri::command] @@ -114,6 +118,17 @@ fn reset_timer( } #[tauri::command] +fn clear_session_count( + timer: State<'_, TimerStateWrapper>, + data: State<'_, AppDataWrapper>, + app: AppHandle, +) -> Result<(), String> { + let mut ts = timer.0.lock().unwrap(); + clear_session_count_only(&mut ts); + persist_timer_snapshot(&ts, &data, &app) +} + +#[tauri::command] fn skip_phase( timer: State<'_, TimerStateWrapper>, data: State<'_, AppDataWrapper>, @@ -455,6 +470,7 @@ pub fn run() { start_timer, pause_timer, reset_timer, + clear_session_count, skip_phase, set_current_task, get_settings, @@ -474,10 +490,32 @@ pub fn run() { #[cfg(test)] mod tests { - use super::fallback_bundled_audio_root; + use super::{clear_session_count_only, fallback_bundled_audio_root}; + use crate::state::{TimerPhase, TimerState}; #[test] fn dev_audio_fallback_points_at_src_tauri() { assert_eq!(fallback_bundled_audio_root(), std::path::PathBuf::from("src-tauri")); } + + #[test] + fn clear_session_count_preserves_phase_and_duration() { + let mut state = TimerState { + phase: TimerPhase::ShortBreak, + remaining_secs: 120, + total_secs: 300, + running: false, + session_count: 4, + current_task_id: Some("task-1".to_string()), + }; + + clear_session_count_only(&mut state); + + assert_eq!(state.phase, TimerPhase::ShortBreak); + assert_eq!(state.remaining_secs, 120); + assert_eq!(state.total_secs, 300); + assert!(!state.running); + assert_eq!(state.session_count, 0); + assert_eq!(state.current_task_id.as_deref(), Some("task-1")); + } } diff --git a/src/App.tsx b/src/App.tsx index 29ac64f..b4aaec7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,6 @@ import { TimerView } from './components/TimerView'; import { TaskList } from './components/TaskList'; import { SettingsPanel } from './components/SettingsPanel'; import { NotificationOverlay } from './components/NotificationOverlay'; -import { AmbientControl } from './components/AmbientControl'; import { useTimerEvents } from './hooks/useTimerEvents'; import { useTaskStore } from './store/taskStore'; import { useAudioStore } from './store/audioStore'; @@ -65,48 +64,35 @@ function App() { <TaskList /> {/* Main area */} - <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}> - {/* Top bar */} - <div + <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', position: 'relative' }}> + <button + onClick={() => setSettingsOpen(true)} + title="Settings" style={{ + position: 'absolute', + top: '14px', + right: '16px', display: 'flex', - alignItems: 'flex-start', - flexWrap: 'wrap', - justifyContent: 'space-between', - gap: '16px', - padding: '12px 16px', - borderBottom: '1px solid var(--line-1)', + alignItems: 'center', + padding: '6px', + border: 'none', + background: 'transparent', + color: 'var(--fg-3)', + cursor: 'pointer', + borderRadius: 'var(--r-2)', + transition: 'color 0.15s ease', + zIndex: 2, + }} + onMouseEnter={(e) => { + (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-1)'; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-3)'; }} > - <AmbientControl /> - - <button - onClick={() => setSettingsOpen(true)} - title="Settings" - style={{ - display: 'flex', - alignItems: 'center', - padding: '6px', - border: 'none', - background: 'transparent', - color: 'var(--fg-3)', - cursor: 'pointer', - borderRadius: 'var(--r-2)', - marginLeft: 'auto', - transition: 'color 0.15s ease', - }} - onMouseEnter={(e) => { - (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-1)'; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLButtonElement).style.color = 'var(--fg-3)'; - }} - > - <GearIcon /> - </button> - </div> + <GearIcon /> + </button> - {/* Timer */} <TimerView /> </div> diff --git a/src/components/AmbientControl.tsx b/src/components/AmbientControl.tsx index dd58235..56a2340 100644 --- a/src/components/AmbientControl.tsx +++ b/src/components/AmbientControl.tsx @@ -56,16 +56,21 @@ export function AmbientControl() { onChange={handleSoundChange} disabled={!available} style={{ + appearance: 'none', + WebkitAppearance: 'none', + MozAppearance: 'none', + colorScheme: 'dark', fontFamily: 'var(--font-sans)', fontSize: '13px', color: available ? 'var(--fg-1)' : 'var(--fg-4)', - background: 'var(--ink-3)', + background: 'var(--ink-2)', border: '1px solid var(--line-2)', borderRadius: 'var(--r-2)', - padding: '6px 10px', + padding: '8px 32px 8px 10px', outline: 'none', minWidth: '132px', flex: '0 0 auto', + lineHeight: 1.3, }} > {SOUND_OPTIONS.map((option) => ( diff --git a/src/components/TimerView.tsx b/src/components/TimerView.tsx index b43973c..47c104a 100644 --- a/src/components/TimerView.tsx +++ b/src/components/TimerView.tsx @@ -2,6 +2,7 @@ import type { CSSProperties } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { useTimerStore, TimerPhase } from '../store/timerStore'; import { useTaskStore } from '../store/taskStore'; +import { AmbientControl } from './AmbientControl'; const RING_SIZE = 280; const STROKE = 8; @@ -78,6 +79,10 @@ export function TimerView() { await invoke('reset_timer'); await syncFromBackend(); }; + const handleClearSessions = async () => { + await invoke('clear_session_count'); + await syncFromBackend(); + }; return ( <div @@ -88,9 +93,14 @@ export function TimerView() { justifyContent: 'center', gap: '20px', flex: 1, - padding: '24px 20px', + padding: '24px 20px 32px', + overflow: 'auto', }} > + <div style={{ width: 'min(100%, 520px)' }}> + <AmbientControl /> + </div> + {/* Eyebrow */} <span className="eyebrow" style={{ letterSpacing: '0.18em' }}> {eyebrowText(phase, sessionCount)} @@ -204,6 +214,7 @@ export function TimerView() { <GhostButton onClick={handleSkip}>Skip</GhostButton> <GhostButton onClick={handleReset}>Reset</GhostButton> + <GhostButton onClick={handleClearSessions}>Clear Streak</GhostButton> </div> </div> ); |
