void-focus 0.3.0-alpha.3

A feature-rich terminal focus timer with task tracking
Documentation
use super::*;
use crate::model::Priority;
use crossterm::event::{KeyCode, KeyEvent};

impl App {
    pub fn close_popup(&mut self) {
        self.popup = None;
        self.input_mode = InputMode::Normal;
        self.input_buffer.clear();
        self.input_buffer2.clear();
        self.input_due_date.clear();
        self.input_tags.clear();
    }

    pub fn submit_popup(&mut self) {
        match self.popup.clone() {
            Some(Popup::AddTask) => {
                let title = self.input_buffer.trim().to_string();
                if title.is_empty() {
                    self.set_status("Title cannot be empty.", true);
                    return;
                }
                let due_date = match self.popup_due_date() {
                    Ok(d) => d,
                    Err(msg) => {
                        self.set_status(msg, true);
                        return;
                    }
                };
                let tags = self.popup_tags();
                if let Err(e) = storage::add_task_full(
                    &self.db,
                    &mut self.data,
                    storage::TaskPayload {
                        title,
                        notes: self.input_buffer2.trim().to_string(),
                        estimated_minutes: self.input_number,
                        priority: self.input_priority,
                        tags,
                        due_date,
                    },
                ) {
                    self.set_status(format!("Save error: {e}"), true);
                    return;
                }
                self.bump_data();
                let indices = self.filtered_task_indices();
                let sel = indices.len().saturating_sub(1);
                self.task_state
                    .select(if indices.is_empty() { None } else { Some(sel) });
                self.close_popup();
                self.set_status("Task added.", false);
            }
            Some(Popup::EditTask(id)) => {
                let title = self.input_buffer.trim().to_string();
                if title.is_empty() {
                    self.set_status("Title cannot be empty.", true);
                    return;
                }
                let due_date = match self.popup_due_date() {
                    Ok(d) => d,
                    Err(msg) => {
                        self.set_status(msg, true);
                        return;
                    }
                };
                let tags = self.popup_tags();
                let notes = self.input_buffer2.trim().to_string();
                let estimate = self.input_number.clamp(1, 480);
                let priority = self.input_priority;
                if let Err(e) = storage::update_task(
                    &self.db,
                    &mut self.data,
                    id,
                    storage::TaskPayload {
                        title,
                        notes,
                        estimated_minutes: estimate,
                        priority,
                        tags,
                        due_date,
                    },
                ) {
                    self.set_status(format!("Save error: {e}"), true);
                    return;
                }
                self.bump_data();
                self.close_popup();
                self.set_status("Task updated.", false);
            }
            Some(Popup::ConfirmDelete(id)) => {
                match storage::delete_task(&self.db, &mut self.data, id) {
                    Ok(true) => {
                        if self.active_task == Some(id) {
                            self.set_active_task(None);
                        }
                        self.bump_data();
                        self.clamp_task_selection_after_mutation();
                        self.set_status("Task deleted.", false);
                        self.check_queue_empty();
                    }
                    Ok(false) => {}
                    Err(e) => self.set_status(format!("Delete error: {e}"), true),
                }
                self.close_popup();
            }
            Some(Popup::EmptyQueueChoice) => {}
            None => {}
        }
    }

    pub fn confirm_delete(&mut self) {
        if let Some(Popup::ConfirmDelete(id)) = self.popup.clone() {
            match storage::delete_task(&self.db, &mut self.data, id) {
                Ok(true) => {
                    if self.active_task == Some(id) {
                        self.set_active_task(None);
                    }
                    self.bump_data();
                    self.clamp_task_selection_after_mutation();
                    self.set_status("Task deleted.", false);
                    self.check_queue_empty();
                }
                Ok(false) => {}
                Err(e) => self.set_status(format!("Delete error: {e}"), true),
            }
            self.close_popup();
        }
    }

    pub(crate) fn handle_popup_key(&mut self, key: KeyEvent) {
        if matches!(self.popup, Some(Popup::EmptyQueueChoice)) {
            match key.code {
                KeyCode::Esc => self.close_popup(),
                KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
                    self.data.empty_queue_behavior = EmptyQueueBehavior::FreeFocus;
                    self.close_popup();
                    self.set_status(
                        "All tasks done — free focus. Sessions log as general focus.",
                        false,
                    );
                }
                KeyCode::Char('p') | KeyCode::Char('P') => {
                    self.data.empty_queue_behavior = EmptyQueueBehavior::PauseTimer;
                    self.close_popup();
                    if self.timer.state == TimerState::Running {
                        self.pause_timer();
                    } else {
                        self.timer.reset();
                    }
                    self.set_status("All tasks done — timer paused.", false);
                }
                KeyCode::Char('a') | KeyCode::Char('A') => {
                    self.close_popup();
                    self.open_add_task();
                }
                _ => {}
            }
            return;
        }
        if matches!(self.popup, Some(Popup::ConfirmDelete(_))) {
            match key.code {
                KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
                    self.close_popup();
                }
                KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
                    self.confirm_delete();
                }
                _ => {}
            }
            return;
        }
        let is_text_field = matches!(
            self.input_field,
            InputField::Title | InputField::Notes | InputField::DueDate | InputField::Tags
        );
        match key.code {
            KeyCode::Esc => {
                self.close_popup();
            }
            KeyCode::Tab | KeyCode::BackTab => {
                let order = [
                    InputField::Title,
                    InputField::Notes,
                    InputField::Estimate,
                    InputField::Priority,
                    InputField::DueDate,
                    InputField::Tags,
                ];
                let idx = order
                    .iter()
                    .position(|f| *f == self.input_field)
                    .unwrap_or(0);
                let next = if key.code == KeyCode::Tab {
                    (idx + 1) % order.len()
                } else {
                    (idx + order.len() - 1) % order.len()
                };
                self.input_field = order[next];
            }
            KeyCode::Enter => {
                self.submit_popup();
            }
            _ => {
                if is_text_field {
                    self.handle_text_input(key);
                } else {
                    self.handle_field_input(key);
                }
            }
        }
    }

    pub(crate) fn handle_text_input(&mut self, key: KeyEvent) {
        // Handle DueDate arrow navigation separately (calendar picker)
        if self.input_field == InputField::DueDate {
            match key.code {
                KeyCode::Left => {
                    self.calendar_date -= chrono::Duration::days(1);
                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
                    return;
                }
                KeyCode::Right => {
                    self.calendar_date += chrono::Duration::days(1);
                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
                    return;
                }
                KeyCode::Up => {
                    self.calendar_date -= chrono::Duration::days(7);
                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
                    return;
                }
                KeyCode::Down => {
                    self.calendar_date += chrono::Duration::days(7);
                    self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
                    return;
                }
                _ => {}
            }
        }
        let buf = match self.input_field {
            InputField::Title => &mut self.input_buffer,
            InputField::Notes => &mut self.input_buffer2,
            InputField::DueDate => &mut self.input_due_date,
            InputField::Tags => &mut self.input_tags,
            _ => return,
        };
        match key.code {
            KeyCode::Backspace => {
                buf.pop();
            }
            KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
                buf.push(c);
            }
            _ => {}
        }
    }

    pub(crate) fn handle_field_input(&mut self, key: KeyEvent) {
        match self.input_field {
            InputField::Estimate => match key.code {
                KeyCode::Char(c) if c.is_ascii_digit() => {
                    let d = c.to_digit(10).unwrap_or(0);
                    self.input_number = (self.input_number.saturating_mul(10) + d).min(480);
                }
                KeyCode::Backspace => {
                    self.input_number /= 10;
                    if self.input_number == 0 {
                        self.input_number = 1;
                    }
                }
                KeyCode::Up => self.input_number = (self.input_number + 5).min(480),
                KeyCode::Down => self.input_number = self.input_number.saturating_sub(5).max(1),
                _ => {}
            },
            InputField::Priority => {
                let next = match key.code {
                    KeyCode::Right | KeyCode::Up | KeyCode::Char(' ') => {
                        match self.input_priority {
                            Priority::Low => Priority::Medium,
                            Priority::Medium => Priority::High,
                            Priority::High => Priority::Low,
                        }
                    }
                    KeyCode::Left | KeyCode::Down => match self.input_priority {
                        Priority::Low => Priority::High,
                        Priority::High => Priority::Medium,
                        Priority::Medium => Priority::Low,
                    },
                    KeyCode::Char('1') => Priority::Low,
                    KeyCode::Char('2') => Priority::Medium,
                    KeyCode::Char('3') => Priority::High,
                    _ => self.input_priority,
                };
                self.input_priority = next;
            }
            _ => {}
        }
    }
}