todo_cli_manikya/
state.rs1use std::collections::HashMap;
2
3use chrono::{DateTime, Local};
4use tui_widget_list::Listable;
5
6use crate::{get_id, ui::render_list_item, Id};
7
8#[derive(Default, serde::Serialize, serde::Deserialize)]
10pub struct Task {
11 pub id: Id,
12 pub desc: String,
13 pub completed: bool,
14 pub last_updated: DateTime<Local>,
15}
16
17impl Task {
18 fn new(task: &str) -> Self {
19 Self {
20 id: get_id(),
21 desc: task.to_owned(),
22 completed: false,
23 last_updated: Local::now(),
24 }
25 }
26 fn mark_complete(&mut self) {
27 self.completed = true;
28 }
29 fn mark_incomplete(&mut self) {
30 self.completed = false;
31 }
32}
33
34pub struct ListItem {
36 pub task: Task,
37 pub selected: bool,
38}
39
40impl Listable for &ListItem {
41 fn height(&self) -> usize {
42 1
43 }
44 fn highlight(self) -> Self
45 where
46 Self: Sized,
47 {
48 self
49 }
50}
51
52impl ratatui::widgets::Widget for &ListItem {
53 fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
54 render_list_item(self, area, buf);
55 }
56}
57
58impl ListItem {
59 pub fn from(task: &Task) -> Self {
60 Self {
61 selected: false,
62 task: Task {
63 completed: task.completed,
64 desc: task.desc.clone(),
65 id: task.id,
66 last_updated: task.last_updated,
67 },
68 }
69 }
70 fn set_selected(&mut self) {
71 self.selected = true;
72 }
73 fn set_unselected(&mut self) {
74 self.selected = false;
75 }
76}
77
78pub struct State {
80 pub ids: Vec<Id>,
81 pub tasks: HashMap<Id, ListItem>,
82 pub selected: Option<usize>,
84}
85
86impl State {
87 pub fn new() -> Self {
88 Self {
89 ids: Vec::new(),
90 tasks: HashMap::new(),
91 selected: None,
92 }
93 }
94
95 pub fn move_selection(&mut self, upwards: bool) {
97 if let Some(selected) = &self.selected {
98 let next = {
99 if upwards {
100 selected.saturating_sub(1)
101 } else {
102 *selected + 1
103 }
104 }
105 .clamp(0, self.ids.len() - 1);
106 self.tasks
107 .get_mut(&self.ids[*selected])
108 .unwrap()
109 .set_unselected();
110 self.tasks.get_mut(&self.ids[next]).unwrap().set_selected();
111 self.selected = Some(next);
112 } else {
113 self.tasks.get_mut(&self.ids[0]).unwrap().set_selected();
114 self.selected = Some(0);
115 }
116 }
117
118 pub fn add_task(&mut self, new_task: &str) {
120 let new_id = get_id();
121 self.ids.insert(0, new_id);
122 self.tasks
123 .insert(new_id, ListItem::from(&Task::new(new_task)));
124 }
125
126 pub fn remove_task(&mut self, id: &Id) -> Option<()> {
128 if self.tasks.remove(id).is_some() {
129 self.ids.retain(|old_id| old_id != id);
130 Some(())
131 } else {
132 None
133 }
134 }
135
136 pub fn remove_task_by_seq(&mut self, idx: usize) {
138 if idx >= self.tasks.len() {
139 return;
140 }
141 self.remove_task(&self.ids[idx].clone());
142 }
143
144 pub fn toggle_task_status(&mut self, idx: usize) -> Option<bool> {
148 let id = self.ids[idx];
149 self.toggle_task_status_by_id(id)
150 }
151
152 pub fn toggle_task_status_by_id(&mut self, id: Id) -> Option<bool> {
156 if let Some(list_item) = self.tasks.get_mut(&id) {
157 if list_item.task.completed {
158 list_item.task.mark_incomplete();
159 Some(false)
160 } else {
161 list_item.task.mark_complete();
162 Some(true)
163 }
164 } else {
165 None
166 }
167 }
168
169 pub fn get_tasks(&self) -> Vec<&Task> {
171 let mut ans = Vec::new();
172 for id in &self.ids {
173 ans.push(&self.tasks.get(id).unwrap().task);
174 }
175 ans
176 }
177}
178
179impl Default for State {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn check_selection_change() {
191 let mut state = State::new();
192 state.add_task("abc");
193 state.add_task("123");
194 state.add_task("xyz");
195 assert!(state.selected.is_none());
196 state.move_selection(true);
197 assert!(state.selected.is_some());
198 assert_eq!(state.selected.unwrap(), 0);
199 state.move_selection(false);
200 assert_eq!(state.selected.unwrap(), 1);
201 assert!(state.tasks.get(&state.ids[1]).unwrap().selected);
202 }
203}