Skip to main content

time_rs/lib/
app.rs

1use crate::lib::db::Db;
2use crate::lib::throbber::Throbber;
3use chrono::{DateTime, Duration, Utc};
4use ratatui::widgets::TableState;
5
6#[derive(Debug)]
7pub enum CurrentScreen {
8    Main,
9    Edit,
10    Add,
11    Exit,
12}
13
14#[derive(Debug)]
15pub enum CurrentlyEditing {
16    Name,
17    Description,
18}
19
20#[derive(Debug)]
21pub struct App {
22    pub timers: Vec<Timer>,
23    pub name_input: String,
24    pub description_input: String,
25    pub currently_editing: Option<CurrentlyEditing>,
26    pub current_screen: CurrentScreen,
27    pub state: TableState,
28    pub selectable_rows: Vec<bool>,
29    pub db: Db,
30    pub throbber: Throbber,
31    pub exit_button_selected: bool, // true for Yes, false for No
32}
33
34impl App {
35    pub fn edit_timer(&mut self) {
36        if let Some(selected) = self.state.selected() {
37            if let Some(timer_index) = self.get_timer_index_from_selection(selected) {
38                self.timers[timer_index].name = self.name_input.clone();
39                self.timers[timer_index].description = self.description_input.clone();
40                self.db
41                    .edit_timer(
42                        &self.timers[timer_index],
43                        &self.name_input,
44                        &self.description_input,
45                    )
46                    .expect("Unable to edit timer");
47                self.currently_editing = None;
48            }
49        }
50    }
51}
52
53#[derive(Debug)]
54pub struct Timer {
55    pub start_time: DateTime<Utc>,
56    pub name: String,
57    pub(crate) duration: Duration,
58    pub description: String,
59    pub id: usize,
60    pub running: bool,
61}
62
63impl App {
64    /// Create a new App instance using the platform-appropriate database path
65    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
66        let db = Db::new_with_default_path()?;
67
68        Ok(App {
69            state: TableState::default().with_selected(1),
70            timers: Vec::new(),
71            current_screen: CurrentScreen::Main,
72            name_input: String::new(),
73            description_input: String::new(),
74            currently_editing: None,
75            selectable_rows: Vec::new(),
76            db,
77            throbber: Throbber::new(),
78            exit_button_selected: false,
79        })
80    }
81
82    pub fn next_row(&mut self) {
83        if self.selectable_rows.is_empty() {
84            return;
85        }
86
87        let current = self.state.selected().unwrap_or(0);
88        let mut next = current;
89
90        loop {
91            next = (next + 1) % self.selectable_rows.len();
92            if self.selectable_rows[next] || next == current {
93                self.state.select(Some(next));
94                break;
95            }
96        }
97        self.state.select(Some(next));
98    }
99
100    pub fn previous_row(&mut self) {
101        if self.selectable_rows.is_empty() {
102            return;
103        }
104
105        let current = self.state.selected().unwrap_or(0);
106        let mut prev = current;
107        loop {
108            prev = (prev + self.selectable_rows.len() - 1) % self.selectable_rows.len();
109            if self.selectable_rows[prev] || prev == current {
110                self.state.select(Some(prev));
111                break;
112            }
113        }
114
115        self.state.select(Some(prev));
116    }
117
118    pub fn add_timer(&mut self) {
119        let mut timer = Timer::new(self.name_input.clone(), self.description_input.clone());
120        match self.timers.last_mut() {
121            Some(t) => t.stop(),
122            None => (),
123        }
124        self.db
125            .add_timer_to_db(&mut timer)
126            .expect("TODO: panic message");
127        self.timers.push(timer);
128        self.name_input = String::new();
129        self.description_input = String::new();
130    }
131
132    pub fn delete_selected_timer(&mut self) -> Result<(), rusqlite::Error> {
133        if let Some(selected) = self.state.selected() {
134            if let Some(timer_index) = self.get_timer_index_from_selection(selected) {
135                self.db.delete_timer(self.timers[timer_index].id)?;
136                self.timers.remove(timer_index);
137            }
138        }
139        Ok(())
140    }
141
142    pub fn toggle_editing(&mut self) {
143        if let Some(edit_mode) = &self.currently_editing {
144            match edit_mode {
145                CurrentlyEditing::Name => {
146                    self.currently_editing = Some(CurrentlyEditing::Description)
147                }
148                CurrentlyEditing::Description => {
149                    self.currently_editing = Some(CurrentlyEditing::Name)
150                }
151            }
152        } else {
153            self.currently_editing = Some(CurrentlyEditing::Name);
154        }
155    }
156
157    pub fn toggle_timer(&mut self) {
158        if let Some(timer) = self.timers.last_mut() {
159            if timer.running {
160                timer.running = false;
161            } else {
162                timer.running = true;
163            }
164        }
165    }
166
167    pub fn toggle_exit_button(&mut self) {
168        self.exit_button_selected = !self.exit_button_selected;
169    }
170
171    /// Convert table selection index to timer index, accounting for non-selectable date rows
172    pub fn get_timer_index_from_selection(&self, selected_index: usize) -> Option<usize> {
173        if selected_index >= self.selectable_rows.len() || !self.selectable_rows[selected_index] {
174            return None;
175        }
176
177        // Count how many selectable rows come before the selected index
178        let timer_index = self.selectable_rows[..selected_index]
179            .iter()
180            .filter(|&&is_selectable| is_selectable)
181            .count();
182
183        if timer_index < self.timers.len() {
184            Some(timer_index)
185        } else {
186            None
187        }
188    }
189}
190
191impl Timer {
192    pub fn new(name: String, description: String) -> Timer {
193        Timer {
194            start_time: Utc::now(),
195            duration: Duration::zero(),
196            name,
197            description,
198            id: 0,
199            running: true,
200        }
201    }
202
203    pub fn stop(&mut self) {
204        self.running = false;
205    }
206
207    pub fn start(&mut self) {
208        self.running = true;
209    }
210    pub fn tick(&mut self) {
211        self.duration += Duration::seconds(1);
212    }
213
214    pub fn formatted_duration(&self) -> String {
215        format!(
216            "{:02}:{:02}:{:02}",
217            self.duration.num_hours(),
218            self.duration.num_minutes() % 60,
219            self.duration.num_seconds() % 60
220        )
221    }
222
223    pub fn formatted_date(&self) -> String {
224        self.start_time.format("%d-%m-%Y").to_string()
225    }
226}