1use super::*;
2use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3
4impl App {
5 pub fn handle_key(&mut self, key: KeyEvent) {
6 if self.searching {
7 self.handle_search_key(key);
8 return;
9 }
10 if self.popup.is_some() {
11 self.handle_popup_key(key);
12 return;
13 }
14 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
15 match key.code {
16 KeyCode::Char('q') => self.should_quit = true,
17 KeyCode::Esc => self.should_quit = true,
18 KeyCode::Char('c') if ctrl => self.should_quit = true,
19 KeyCode::Char('s') if ctrl => self.export_backup(),
20 KeyCode::Char('1') => self.tab = FocusTab::Dashboard,
21 KeyCode::Char('2') => self.tab = FocusTab::Tasks,
22 KeyCode::Char('3') => self.tab = FocusTab::Stats,
23 KeyCode::Char('4') => self.tab = FocusTab::Settings,
24 KeyCode::Char('5') | KeyCode::Char('h') => self.tab = FocusTab::Help,
25 KeyCode::Tab => self.next_tab(),
26 KeyCode::BackTab => self.prev_tab(),
27 _ => match self.tab {
28 FocusTab::Dashboard => self.handle_dashboard_key(key),
29 FocusTab::Tasks => self.handle_tasks_key(key),
30 FocusTab::Stats => self.handle_stats_key(key),
31 FocusTab::Settings => self.handle_settings_key(key),
32 FocusTab::Help => {}
33 },
34 }
35 }
36
37 pub(crate) fn next_tab(&mut self) {
38 let cur = FocusTab::all()
39 .iter()
40 .position(|t| *t == self.tab)
41 .unwrap_or(0);
42 self.tab = FocusTab::all()[(cur + 1) % FocusTab::all().len()];
43 }
44
45 pub(crate) fn prev_tab(&mut self) {
46 let cur = FocusTab::all()
47 .iter()
48 .position(|t| *t == self.tab)
49 .unwrap_or(0);
50 let n = FocusTab::all().len();
51 self.tab = FocusTab::all()[(cur + n - 1) % n];
52 }
53
54 pub(crate) fn handle_dashboard_key(&mut self, key: KeyEvent) {
55 match key.code {
56 KeyCode::Char('s') | KeyCode::Char(' ') => self.toggle_timer(),
57 KeyCode::Char('p') => self.pause_timer(),
58 KeyCode::Char('r') => self.reset_timer(),
59 KeyCode::Char('n') => {
60 self.timer.skip();
61 self.on_timer_finished(true);
62 }
63 KeyCode::Char('m') => self.cycle_mode(),
64 KeyCode::Char('+') | KeyCode::Char('=') => self.adjust_minutes(1),
65 KeyCode::Char('-') | KeyCode::Char('_') => self.adjust_minutes(-1),
66 KeyCode::Char('a') => self.open_add_task(),
67 KeyCode::Char('f') => {
68 if let Some(id) = self.dashboard_selected_task_id() {
69 self.set_active_task(Some(id));
70 self.set_status("Task set as active.", false);
71 }
72 }
73 KeyCode::Char('g') => {
74 self.cycle_task_filter();
75 }
76 KeyCode::Char('t') => {
77 self.cycle_tag_filter();
78 }
79 KeyCode::Char('z') => {
80 self.zen_mode = !self.zen_mode;
81 self.set_status(
82 format!("Zen mode {}.", if self.zen_mode { "on" } else { "off" }),
83 false,
84 );
85 }
86 KeyCode::Down | KeyCode::Char('j') => self.move_dashboard_task_selection(1),
87 KeyCode::Up | KeyCode::Char('k') => self.move_dashboard_task_selection(-1),
88 KeyCode::Enter => {
89 if let Some(id) = self.dashboard_selected_task_id() {
90 self.set_active_task(Some(id));
91 self.persist_data(|db, data| storage::cycle_task_status(db, data, id));
92 let status = self
93 .data
94 .tasks
95 .iter()
96 .find(|t| t.id == id)
97 .map(|t| t.status);
98 if let Some(status) = status {
99 if status == crate::model::TaskStatus::Done {
100 self.active_task = None;
101 self.data.active_task_id = None;
102 self.persist(|db| db.persist_active_task(None));
103 self.maybe_advance_task();
104 self.check_queue_empty();
105 }
106 self.bump_data();
107 self.clamp_dashboard_task_selection();
108 self.set_status(format!("Task status: {}", status.label()), false);
109 }
110 } else {
111 self.cycle_active_task_status();
112 }
113 }
114 KeyCode::Char('x') => {
115 if let Some(id) = self.dashboard_selected_task_id() {
116 self.persist_data(|db, data| storage::mark_task_done(db, data, id));
117 if self.active_task == Some(id) {
118 self.active_task = None;
119 self.data.active_task_id = None;
120 self.persist(|db| db.persist_active_task(None));
121 self.maybe_advance_task();
122 }
123 self.bump_data();
124 self.clamp_dashboard_task_selection();
125 self.check_queue_empty();
126 self.set_status("Task marked done.", false);
127 } else {
128 self.mark_active_task_done();
129 }
130 }
131 KeyCode::Char('e') | KeyCode::Char('E') => self.end_session(),
132 _ => {}
133 }
134 }
135
136 pub(crate) fn handle_stats_key(&mut self, key: KeyEvent) {
137 if self.recent_sessions.is_empty() {
138 if matches!(key.code, KeyCode::Char('e') | KeyCode::Char('E')) {
139 self.end_session();
140 }
141 return;
142 }
143 let n = self.recent_sessions.len();
144 match key.code {
145 KeyCode::Down | KeyCode::Char('j') => {
146 self.stats_session_selected = (self.stats_session_selected + 1) % n;
147 }
148 KeyCode::Up | KeyCode::Char('k') => {
149 self.stats_session_selected = if self.stats_session_selected == 0 {
150 n - 1
151 } else {
152 self.stats_session_selected - 1
153 };
154 }
155 KeyCode::Char('d') => {
156 let id = self.recent_sessions[self.stats_session_selected].id;
157 self.persist_data(|db, data| storage::delete_session(db, data, id));
158 self.bump_data();
159 self.set_status("Session deleted.", false);
160 }
161 KeyCode::Char('+') | KeyCode::Char('=') => {
162 let entry = &self.recent_sessions[self.stats_session_selected];
163 let new_mins = entry.record.minutes.saturating_add(5);
164 let id = entry.id;
165 self.persist_data(|db, data| {
166 storage::adjust_session_minutes(db, data, id, new_mins)
167 });
168 self.bump_data();
169 }
170 KeyCode::Char('-') => {
171 let entry = &self.recent_sessions[self.stats_session_selected];
172 let new_mins = entry.record.minutes.saturating_sub(5).max(1);
173 let id = entry.id;
174 self.persist_data(|db, data| {
175 storage::adjust_session_minutes(db, data, id, new_mins)
176 });
177 self.bump_data();
178 }
179 KeyCode::Char('e') | KeyCode::Char('E') => self.end_session(),
180 _ => {}
181 }
182 }
183
184 pub(crate) fn handle_search_key(&mut self, key: KeyEvent) {
185 match key.code {
186 KeyCode::Esc => {
187 self.searching = false;
188 self.task_search.clear();
189 }
190 KeyCode::Enter => {
191 self.searching = false;
192 }
193 KeyCode::Backspace => {
194 self.task_search.pop();
195 }
196 KeyCode::Char(c) => {
197 self.task_search.push(c);
198 }
199 _ => {}
200 }
201 }
202
203 pub(crate) fn handle_tasks_key(&mut self, key: KeyEvent) {
204 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
205 match key.code {
206 KeyCode::Char('f') => {
207 if let Some(id) = self.selected_task_id() {
208 self.start_focus_on_task(id);
209 }
210 }
211 KeyCode::Char('g') => {
212 self.cycle_task_filter();
213 }
214 KeyCode::Char('/') => {
215 self.searching = true;
216 self.task_search.clear();
217 }
218 KeyCode::Char('t') => {
219 if let Some(id) = self.selected_task_id() {
220 self.persist_data(|db, data| storage::toggle_today(db, data, id));
221 self.bump_data();
222 }
223 }
224 KeyCode::Char('a') => self.open_add_task(),
225 KeyCode::Char('e') => self.open_edit_task(),
226 KeyCode::Char('d') => self.open_confirm_delete(),
227 KeyCode::Enter => {
228 if let Some(id) = self.selected_task_id() {
229 self.persist_data(|db, data| storage::cycle_task_status(db, data, id));
230 self.bump_data();
231 }
232 }
233 KeyCode::Char(' ') => {
234 if let Some(id) = self.selected_task_id() {
235 self.set_active_task(Some(id));
236 self.set_status("Task set as active for the timer.", false);
237 }
238 }
239 KeyCode::Char('1') => {
240 if let Some(id) = self.selected_task_id() {
241 self.persist_data(|db, data| {
242 storage::set_priority(db, data, id, Priority::Low)
243 });
244 self.bump_data();
245 }
246 }
247 KeyCode::Char('2') => {
248 if let Some(id) = self.selected_task_id() {
249 self.persist_data(|db, data| {
250 storage::set_priority(db, data, id, Priority::Medium)
251 });
252 self.bump_data();
253 }
254 }
255 KeyCode::Char('3') => {
256 if let Some(id) = self.selected_task_id() {
257 self.persist_data(|db, data| {
258 storage::set_priority(db, data, id, Priority::High)
259 });
260 self.bump_data();
261 }
262 }
263 KeyCode::Down | KeyCode::Char('j') if !ctrl => self.move_task_selection(1),
264 KeyCode::Up | KeyCode::Char('k') if !ctrl => self.move_task_selection(-1),
265 KeyCode::Down | KeyCode::Char('j') if ctrl => {
266 if let Some(id) = self.selected_task_id() {
267 self.persist_data(|db, data| storage::move_task(db, data, id, 1));
268 self.bump_data();
269 }
270 }
271 KeyCode::Up | KeyCode::Char('k') if ctrl => {
272 if let Some(id) = self.selected_task_id() {
273 self.persist_data(|db, data| storage::move_task(db, data, id, -1));
274 self.bump_data();
275 }
276 }
277 KeyCode::PageDown => self.move_task_selection(8),
278 KeyCode::PageUp => self.move_task_selection(-8),
279 KeyCode::Home => {
280 let len = self.filtered_task_indices().len();
281 if len > 0 {
282 self.task_state.select(Some(0));
283 }
284 }
285 KeyCode::End => {
286 let len = self.filtered_task_indices().len();
287 if len > 0 {
288 self.task_state.select(Some(len - 1));
289 }
290 }
291 _ => {}
292 }
293 }
294
295 pub(crate) fn move_task_selection(&mut self, delta: i32) {
296 let len = self.filtered_task_indices().len();
297 if len == 0 {
298 return;
299 }
300 let cur = self.task_state.selected().unwrap_or(0) as i32;
301 let new = (cur + delta).clamp(0, len as i32 - 1) as usize;
302 self.task_state.select(Some(new));
303 }
304}