1use super::*;
2use crate::model::TimerMode;
3
4impl App {
5 pub(crate) fn maybe_complete_task_estimate(&mut self, task_id: Option<u64>) {
6 let Some(id) = task_id else {
7 return;
8 };
9 let estimated = self
10 .data
11 .tasks
12 .iter()
13 .find(|t| t.id == id)
14 .map(|t| (t.title.clone(), t.actual_minutes, t.estimated_minutes));
15 let Some((title, actual, estimate)) = estimated else {
16 return;
17 };
18 if actual < estimate {
19 return;
20 }
21 match self.data.estimate_complete {
22 EstimateCompleteBehavior::Nudge => {
23 self.set_status(
24 format!("Estimate reached for \"{title}\" — mark done?"),
25 false,
26 );
27 }
28 EstimateCompleteBehavior::AutoDone => {
29 self.persist_data(|db, data| storage::mark_task_done(db, data, id));
30 if self.active_task == Some(id) {
31 self.active_task = None;
32 self.data.active_task_id = None;
33 self.persist(|db| db.persist_active_task(None));
34 }
35 self.bump_data();
36 self.set_status(format!("\"{title}\" auto-completed (estimate met)."), false);
37 self.check_queue_empty();
38 }
39 EstimateCompleteBehavior::None => {}
40 }
41 }
42
43 pub fn end_session(&mut self) {
44 if self.timer.state == TimerState::Running {
45 self.pause_timer();
46 }
47 let today = storage::today_focus_minutes(&self.data);
48 let goal = self.data.daily_goal_minutes;
49 let queue_note = if self.queue_empty() {
50 "all tasks done"
51 } else {
52 "tasks remain"
53 };
54 self.set_status(
55 format!(
56 "Session ended — today {}/{} min · goal streak {} days · {queue_note}",
57 today, goal, self.data.goal_streak_days
58 ),
59 false,
60 );
61 }
62
63 pub fn on_tick(&mut self) {
64 let _ = storage::ensure_today_reset(&self.db, &mut self.data);
65 let just_finished = self.timer.tick();
66 if just_finished {
67 self.on_timer_finished(false);
68 }
69 if self.status.is_some()
70 && !self.status_error
71 && self.last_status_set.elapsed() > Duration::from_secs(4)
72 {
73 self.status = None;
74 }
75 }
76
77 pub(crate) fn on_timer_finished(&mut self, skipped: bool) {
78 let mode = self.timer.mode;
79 if mode == TimerMode::Focus {
80 let mins = self.elapsed_minutes(skipped);
81 let task_id = self.active_task;
82 self.persist_data(|db, data| {
83 storage::record_focus_session(db, data, mins, task_id, mode)
84 });
85 self.maybe_complete_task_estimate(task_id);
86 if self.data.sound_enabled {
87 sound::play_finish();
88 }
89 if self.data.notify_on_finish {
90 let msg = if skipped {
91 format!("Logged {} min (skipped early)", mins)
92 } else {
93 format!("+{} min logged — time for a break", mins)
94 };
95 let kind = if skipped {
96 sound::NotifyKind::SessionSkipped
97 } else {
98 sound::NotifyKind::FocusComplete
99 };
100 sound::notify_typed(kind, "Void · Focus complete", &msg);
101 }
102 self.set_status(
103 format!(
104 "Focus {}: +{} min",
105 if skipped { "skipped" } else { "complete" },
106 mins
107 ),
108 false,
109 );
110 self.maybe_advance_task();
111 self.bump_data();
112 if !skipped {
113 self.persist_timer_state();
114 }
115 self.advance_to_break();
116 } else if mode == TimerMode::Custom {
117 let mins = self.elapsed_minutes(skipped);
118 let task_id = self.active_task;
119 self.persist_data(|db, data| {
120 storage::record_focus_session(db, data, mins, task_id, mode)
121 });
122 if self.data.sound_enabled {
123 sound::play_finish();
124 }
125 self.set_status(format!("Custom session complete: +{} min", mins), false);
126 self.bump_data();
127 self.timer.configure(TimerMode::Focus);
128 self.persist_timer_state();
129 } else {
130 let break_mins = self.elapsed_minutes(false);
131 self.persist_data(|db, data| storage::record_break_session(db, data, mode, break_mins));
132 if self.data.sound_enabled {
133 sound::play_finish();
134 }
135 if self.data.notify_on_finish {
136 sound::notify_typed(
137 sound::NotifyKind::BreakComplete,
138 "Void · Break over",
139 "Break finished — ready to focus again",
140 );
141 }
142 self.set_status("Break finished. Ready for focus.", false);
143 self.bump_data();
144 self.advance_to_focus();
145 }
146 }
147
148 pub(crate) fn advance_to_break(&mut self) {
149 let long_break = self.timer.completed_focus_sessions > 0
150 && self
151 .timer
152 .completed_focus_sessions
153 .is_multiple_of(self.timer.config.long_break_every);
154 let next = if long_break {
155 TimerMode::LongBreak
156 } else {
157 TimerMode::ShortBreak
158 };
159 self.timer.configure(next);
160 self.persist_timer_state();
161 if self.data.auto_start_breaks {
162 self.timer.start();
163 if self.data.sound_enabled {
164 sound::play_start();
165 }
166 self.set_status(format!("{} started.", next.label()), false);
167 }
168 }
169
170 pub(crate) fn advance_to_focus(&mut self) {
171 self.timer.configure(TimerMode::Focus);
172 self.persist_timer_state();
173 if self.queue_empty() && self.data.empty_queue_behavior == EmptyQueueBehavior::PauseTimer {
174 self.set_status("All tasks done — timer waiting. [E] end session", false);
175 return;
176 }
177 self.auto_pick_task_if_needed();
178 if self.data.auto_start_focus {
179 self.timer.start();
180 if self.data.sound_enabled {
181 sound::play_start();
182 }
183 self.set_status("Focus started.", false);
184 }
185 }
186
187 pub fn toggle_timer(&mut self) {
188 if self.timer.state == TimerState::Running {
189 self.pause_timer();
190 } else {
191 self.start_timer();
192 }
193 }
194
195 pub fn start_timer(&mut self) {
196 if self.timer.state == TimerState::Running {
197 return;
198 }
199 if self.timer.state == TimerState::Finished {
200 self.timer.reset();
201 }
202 if self.timer.mode == TimerMode::Focus {
203 self.auto_pick_task_if_needed();
204 }
205 self.timer.start();
206 if self.data.sound_enabled {
207 sound::play_start();
208 }
209 self.set_status("Timer started.", false);
210 }
211
212 pub fn pause_timer(&mut self) {
213 if self.timer.state != TimerState::Running {
214 return;
215 }
216 let elapsed = self.timer.current_elapsed_seconds();
217 self.timer.pause();
218 if self.data.sound_enabled {
219 sound::play_pause();
220 }
221 let active_minutes = (elapsed / 60).max(1);
222 self.set_status(
223 format!(
224 "Paused at {} ({} min in).",
225 self.timer.format_remaining(),
226 active_minutes
227 ),
228 false,
229 );
230 }
231
232 pub fn reset_timer(&mut self) {
233 self.timer.reset();
234 self.set_status("Timer reset.", false);
235 }
236
237 pub fn cycle_mode(&mut self) {
238 if self.timer.state == TimerState::Running || self.timer.state == TimerState::Paused {
239 self.set_status("Stop the timer before changing mode.", true);
240 return;
241 }
242 let next = match self.timer.mode {
243 TimerMode::Focus => TimerMode::ShortBreak,
244 TimerMode::ShortBreak => TimerMode::LongBreak,
245 TimerMode::LongBreak => TimerMode::Custom,
246 TimerMode::Custom => TimerMode::Focus,
247 };
248 self.timer.configure(next);
249 self.set_status(format!("Mode: {}", next.label()), false);
250 }
251
252 pub fn adjust_minutes(&mut self, delta: i32) {
253 if self.timer.state == TimerState::Running || self.timer.state == TimerState::Paused {
254 self.set_status("Stop the timer before adjusting duration.", true);
255 return;
256 }
257 match self.timer.mode {
258 TimerMode::Focus => {
259 let cur = self.timer.config.focus_minutes as i32 + delta;
260 let v = cur.clamp(1, 240) as u32;
261 self.timer.set_focus_minutes(v);
262 self.data.focus_minutes = v;
263 }
264 TimerMode::ShortBreak => {
265 let cur = self.timer.config.short_break_minutes as i32 + delta;
266 let v = cur.clamp(1, 60) as u32;
267 self.timer.config.short_break_minutes = v;
268 self.data.short_break_minutes = v;
269 self.timer.total_seconds = self.timer.duration_seconds();
270 }
271 TimerMode::LongBreak => {
272 let cur = self.timer.config.long_break_minutes as i32 + delta;
273 let v = cur.clamp(1, 120) as u32;
274 self.timer.config.long_break_minutes = v;
275 self.data.long_break_minutes = v;
276 self.timer.total_seconds = self.timer.duration_seconds();
277 }
278 TimerMode::Custom => {
279 let cur = self.timer.custom_minutes as i32 + delta;
280 let v = cur.clamp(1, 240) as u32;
281 self.timer.set_custom_minutes(v);
282 }
283 }
284 if let Err(e) = self.db.persist_timer_settings(&self.data) {
285 self.set_status(format!("Save error: {e}"), true);
286 }
287 self.set_status(
288 format!(
289 "{} length: {} min",
290 self.timer.mode.label(),
291 self.timer.duration_seconds() / 60
292 ),
293 false,
294 );
295 }
296}