Skip to main content

void/app/
popups.rs

1use super::*;
2use crate::model::Priority;
3use crossterm::event::{KeyCode, KeyEvent};
4
5impl App {
6    pub fn close_popup(&mut self) {
7        self.popup = None;
8        self.input_mode = InputMode::Normal;
9        self.input_buffer.clear();
10        self.input_buffer2.clear();
11        self.input_due_date.clear();
12        self.input_tags.clear();
13    }
14
15    pub fn submit_popup(&mut self) {
16        match self.popup.clone() {
17            Some(Popup::AddTask) => {
18                let title = self.input_buffer.trim().to_string();
19                if title.is_empty() {
20                    self.set_status("Title cannot be empty.", true);
21                    return;
22                }
23                let due_date = match self.popup_due_date() {
24                    Ok(d) => d,
25                    Err(msg) => {
26                        self.set_status(msg, true);
27                        return;
28                    }
29                };
30                let tags = self.popup_tags();
31                if let Err(e) = storage::add_task_full(
32                    &self.db,
33                    &mut self.data,
34                    storage::TaskPayload {
35                        title,
36                        notes: self.input_buffer2.trim().to_string(),
37                        estimated_minutes: self.input_number,
38                        priority: self.input_priority,
39                        tags,
40                        due_date,
41                    },
42                ) {
43                    self.set_status(format!("Save error: {e}"), true);
44                    return;
45                }
46                self.bump_data();
47                let indices = self.filtered_task_indices();
48                let sel = indices.len().saturating_sub(1);
49                self.task_state
50                    .select(if indices.is_empty() { None } else { Some(sel) });
51                self.close_popup();
52                self.set_status("Task added.", false);
53            }
54            Some(Popup::EditTask(id)) => {
55                let title = self.input_buffer.trim().to_string();
56                if title.is_empty() {
57                    self.set_status("Title cannot be empty.", true);
58                    return;
59                }
60                let due_date = match self.popup_due_date() {
61                    Ok(d) => d,
62                    Err(msg) => {
63                        self.set_status(msg, true);
64                        return;
65                    }
66                };
67                let tags = self.popup_tags();
68                let notes = self.input_buffer2.trim().to_string();
69                let estimate = self.input_number.clamp(1, 480);
70                let priority = self.input_priority;
71                if let Err(e) = storage::update_task(
72                    &self.db,
73                    &mut self.data,
74                    id,
75                    storage::TaskPayload {
76                        title,
77                        notes,
78                        estimated_minutes: estimate,
79                        priority,
80                        tags,
81                        due_date,
82                    },
83                ) {
84                    self.set_status(format!("Save error: {e}"), true);
85                    return;
86                }
87                self.bump_data();
88                self.close_popup();
89                self.set_status("Task updated.", false);
90            }
91            Some(Popup::ConfirmDelete(id)) => {
92                match storage::delete_task(&self.db, &mut self.data, id) {
93                    Ok(true) => {
94                        if self.active_task == Some(id) {
95                            self.set_active_task(None);
96                        }
97                        self.bump_data();
98                        self.clamp_task_selection_after_mutation();
99                        self.set_status("Task deleted.", false);
100                        self.check_queue_empty();
101                    }
102                    Ok(false) => {}
103                    Err(e) => self.set_status(format!("Delete error: {e}"), true),
104                }
105                self.close_popup();
106            }
107            Some(Popup::EmptyQueueChoice) => {}
108            None => {}
109        }
110    }
111
112    pub fn confirm_delete(&mut self) {
113        if let Some(Popup::ConfirmDelete(id)) = self.popup.clone() {
114            match storage::delete_task(&self.db, &mut self.data, id) {
115                Ok(true) => {
116                    if self.active_task == Some(id) {
117                        self.set_active_task(None);
118                    }
119                    self.bump_data();
120                    self.clamp_task_selection_after_mutation();
121                    self.set_status("Task deleted.", false);
122                    self.check_queue_empty();
123                }
124                Ok(false) => {}
125                Err(e) => self.set_status(format!("Delete error: {e}"), true),
126            }
127            self.close_popup();
128        }
129    }
130
131    pub(crate) fn handle_popup_key(&mut self, key: KeyEvent) {
132        if matches!(self.popup, Some(Popup::EmptyQueueChoice)) {
133            match key.code {
134                KeyCode::Esc => self.close_popup(),
135                KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
136                    self.data.empty_queue_behavior = EmptyQueueBehavior::FreeFocus;
137                    self.close_popup();
138                    self.set_status(
139                        "All tasks done — free focus. Sessions log as general focus.",
140                        false,
141                    );
142                }
143                KeyCode::Char('p') | KeyCode::Char('P') => {
144                    self.data.empty_queue_behavior = EmptyQueueBehavior::PauseTimer;
145                    self.close_popup();
146                    if self.timer.state == TimerState::Running {
147                        self.pause_timer();
148                    } else {
149                        self.timer.reset();
150                    }
151                    self.set_status("All tasks done — timer paused.", false);
152                }
153                KeyCode::Char('a') | KeyCode::Char('A') => {
154                    self.close_popup();
155                    self.open_add_task();
156                }
157                _ => {}
158            }
159            return;
160        }
161        if matches!(self.popup, Some(Popup::ConfirmDelete(_))) {
162            match key.code {
163                KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
164                    self.close_popup();
165                }
166                KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
167                    self.confirm_delete();
168                }
169                _ => {}
170            }
171            return;
172        }
173        let is_text_field = matches!(
174            self.input_field,
175            InputField::Title | InputField::Notes | InputField::DueDate | InputField::Tags
176        );
177        match key.code {
178            KeyCode::Esc => {
179                self.close_popup();
180            }
181            KeyCode::Tab | KeyCode::BackTab => {
182                let order = [
183                    InputField::Title,
184                    InputField::Notes,
185                    InputField::Estimate,
186                    InputField::Priority,
187                    InputField::DueDate,
188                    InputField::Tags,
189                ];
190                let idx = order
191                    .iter()
192                    .position(|f| *f == self.input_field)
193                    .unwrap_or(0);
194                let next = if key.code == KeyCode::Tab {
195                    (idx + 1) % order.len()
196                } else {
197                    (idx + order.len() - 1) % order.len()
198                };
199                self.input_field = order[next];
200            }
201            KeyCode::Enter => {
202                self.submit_popup();
203            }
204            _ => {
205                if is_text_field {
206                    self.handle_text_input(key);
207                } else {
208                    self.handle_field_input(key);
209                }
210            }
211        }
212    }
213
214    pub(crate) fn handle_text_input(&mut self, key: KeyEvent) {
215        // Handle DueDate arrow navigation separately (calendar picker)
216        if self.input_field == InputField::DueDate {
217            match key.code {
218                KeyCode::Left => {
219                    self.calendar_date -= chrono::Duration::days(1);
220                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
221                    return;
222                }
223                KeyCode::Right => {
224                    self.calendar_date += chrono::Duration::days(1);
225                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
226                    return;
227                }
228                KeyCode::Up => {
229                    self.calendar_date -= chrono::Duration::days(7);
230                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
231                    return;
232                }
233                KeyCode::Down => {
234                    self.calendar_date += chrono::Duration::days(7);
235                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
236                    return;
237                }
238                _ => {}
239            }
240        }
241        let buf = match self.input_field {
242            InputField::Title => &mut self.input_buffer,
243            InputField::Notes => &mut self.input_buffer2,
244            InputField::DueDate => &mut self.input_due_date,
245            InputField::Tags => &mut self.input_tags,
246            _ => return,
247        };
248        match key.code {
249            KeyCode::Backspace => {
250                buf.pop();
251            }
252            KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
253                buf.push(c);
254            }
255            _ => {}
256        }
257    }
258
259    pub(crate) fn handle_field_input(&mut self, key: KeyEvent) {
260        match self.input_field {
261            InputField::Estimate => match key.code {
262                KeyCode::Char(c) if c.is_ascii_digit() => {
263                    let d = c.to_digit(10).unwrap_or(0);
264                    self.input_number = (self.input_number.saturating_mul(10) + d).min(480);
265                }
266                KeyCode::Backspace => {
267                    self.input_number /= 10;
268                    if self.input_number == 0 {
269                        self.input_number = 1;
270                    }
271                }
272                KeyCode::Up => self.input_number = (self.input_number + 5).min(480),
273                KeyCode::Down => self.input_number = self.input_number.saturating_sub(5).max(1),
274                _ => {}
275            },
276            InputField::Priority => {
277                let next = match key.code {
278                    KeyCode::Right | KeyCode::Up | KeyCode::Char(' ') => {
279                        match self.input_priority {
280                            Priority::Low => Priority::Medium,
281                            Priority::Medium => Priority::High,
282                            Priority::High => Priority::Low,
283                        }
284                    }
285                    KeyCode::Left | KeyCode::Down => match self.input_priority {
286                        Priority::Low => Priority::High,
287                        Priority::High => Priority::Medium,
288                        Priority::Medium => Priority::Low,
289                    },
290                    KeyCode::Char('1') => Priority::Low,
291                    KeyCode::Char('2') => Priority::Medium,
292                    KeyCode::Char('3') => Priority::High,
293                    _ => self.input_priority,
294                };
295                self.input_priority = next;
296            }
297            _ => {}
298        }
299    }
300}