diff --git a/src/todo.rs b/src/todo.rs index b3ea4a7..e0a61b5 100644 --- a/src/todo.rs +++ b/src/todo.rs @@ -1,35 +1,41 @@ -use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme}; -use iced::keyboard::key; -use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text}; -use iced::widget::text::danger; -use rusqlite::Connection; use crate::db; +use iced::keyboard::key; +use iced::widget::text::danger; +use iced::widget::{ + button, center, checkbox, column, combo_box, row, scrollable, text_input, Text, +}; +use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme}; +use rusqlite::Connection; #[derive(Debug)] pub struct TaskColumn { - pub(crate) id: usize, - pub(crate) checked: bool, - pub(crate) value: String, + pub(crate) id: usize, + pub(crate) checked: bool, + pub(crate) value: String, } pub struct TaskData { - db_id: usize, - checked: bool, - value: String, - edit: bool, - can_update: bool, - can_delete: bool, - already_exists: bool, + db_id: usize, + checked: bool, + value: String, + edit: bool, + can_update: bool, + can_delete: bool, + already_exists: bool, } pub(crate) struct Todo { - new_task: String, - updated_task: String, - tasks: Vec, - completed_tasks: usize, - local_storage: bool, - conn: Connection, - select_all: bool, + new_task: String, + updated_task: String, + tasks: Vec, + completed_tasks: usize, + local_storage: bool, + conn: Connection, + select_all: bool, + task_list_input: String, + task_list_state: combo_box::State, + task_list: Vec, + current_task_group: Option, } #[derive(Debug, Clone)] @@ -39,11 +45,13 @@ pub enum Message { EditTask(usize), DeleteTask(usize), DeleteAll, - ContentUpdated(bool, String), + ContentUpdated(ContentType, String), Event(Event), TaskPush(Option), StorageToggle(bool), ToggleUnselect(bool), + SelectedTaskList(String), + AddTaskList, } pub enum ChangeType { @@ -51,6 +59,13 @@ pub enum ChangeType { Value, } +#[derive(Clone, Debug)] +enum ContentType { + NewTask, + ExistingTask, + NewList, +} + impl Default for Todo { fn default() -> Self { Todo::new() @@ -65,38 +80,61 @@ impl Todo { tasks: Vec::new(), completed_tasks: 0, local_storage: true, // todo eventually get this info from db - if no db set it to true - conn: db::startup("./todolist-db.db3".as_ref()).expect("[ERROR] Failed to access the local storage"), + conn: db::startup("./todolist-db.db3".as_ref()) + .expect("[ERROR] Failed to access the local storage"), select_all: false, + task_list_input: String::from(""), + task_list_state: Default::default(), + task_list: vec![String::from("general")], + current_task_group: Some(String::from("")), }; - + Self::load_data_from_db(&mut init); - + init.task_list_state = combo_box::State::new(init.task_list.clone()); + init.current_task_group = Some(init.task_list[0].clone()); + init } - pub(crate) fn update(&mut self, message: Message) -> Task { + pub(crate) fn update(&mut self, message: Message) -> Task { match message { Message::ContentUpdated(new, value) => { - if new { + /*if new { self.new_task = String::from(&value); } else { self.updated_task = String::from(&value) + }*/ + + match new { + ContentType::NewTask => self.new_task = String::from(&value), + ContentType::ExistingTask => self.updated_task = String::from(&value), + ContentType::NewList => { + self.task_list_input = String::from(&value); + return Task::none(); + } } - + for task in &mut self.tasks { task.already_exists = task.value == value.to_string(); } - + Task::none() } Message::AddTask => { - if self.new_task.is_empty() { return Task::none(); } - if self.duplicate_exists(None) { return Task::none(); } - - let result = db::insert_task(&self.conn, TaskColumn { - id: 0, - checked: false, - value: self.new_task.to_string(), - }); + if self.new_task.is_empty() { + return Task::none(); + } + if self.duplicate_exists(None) { + return Task::none(); + } + + let result = db::insert_task( + &self.conn, + TaskColumn { + id: 0, + checked: false, + value: self.new_task.to_string(), + }, + ); match result { Ok(t) => { @@ -110,65 +148,86 @@ impl Todo { already_exists: false, }; self.tasks.push(data); - }, + } Err(e) => eprintln!("[ERROR] Failed to insert new task into DB:\n{e}"), } self.new_task = String::new(); Task::none() - }, + } Message::DeleteTask(index) => { if self.tasks[index].checked { self.completed_tasks -= 1; } - + if let Err(e) = db::delete_tasks(&self.conn, Some(self.tasks[index].db_id)) { - eprintln!("[ERROR] Failed to delete task '{}':\n{e}", self.tasks[index].value); + eprintln!( + "[ERROR] Failed to delete task '{}':\n{e}", + self.tasks[index].value + ); } - + self.tasks.remove(index); - Task::none() } Message::CheckTask(choice, id) => { - self.completed_tasks = if choice { self.completed_tasks + 1 } else { self.completed_tasks - 1 }; + self.completed_tasks = if choice { + self.completed_tasks + 1 + } else { + self.completed_tasks - 1 + }; self.tasks[id].checked = choice; - let result = db::update_task(&self.conn, TaskColumn { - id: self.tasks[id].db_id, - checked: self.tasks[id].checked, - value: self.tasks[id].value.to_string(), - }, ChangeType::Checked); - + let result = db::update_task( + &self.conn, + TaskColumn { + id: self.tasks[id].db_id, + checked: self.tasks[id].checked, + value: self.tasks[id].value.to_string(), + }, + ChangeType::Checked, + ); + if let Err(e) = result { eprintln!("[ERROR] Failed to update checkbox in local storage:\n{e}"); } Task::none() - }, + } Message::EditTask(index) => { let set_update; if self.tasks[index].edit { - if self.updated_task.is_empty() { return Task::none(); } - if self.duplicate_exists(Some(&self.tasks[index].value)) { return Task::none(); } + if self.updated_task.is_empty() { + return Task::none(); + } + if self.duplicate_exists(Some(&self.tasks[index].value)) { + return Task::none(); + } self.tasks[index].value = self.updated_task.clone(); - let result = db::update_task(&self.conn, TaskColumn { - id: self.tasks[index].db_id, - checked: self.tasks[index].checked, - value: self.tasks[index].value.to_string(), - }, ChangeType::Value); - + let result = db::update_task( + &self.conn, + TaskColumn { + id: self.tasks[index].db_id, + checked: self.tasks[index].checked, + value: self.tasks[index].value.to_string(), + }, + ChangeType::Value, + ); + if let Err(e) = result { - eprintln!("[ERROR] Failed to update task '{}' in local storage:\n{e}", self.tasks[index].value); + eprintln!( + "[ERROR] Failed to update task '{}' in local storage:\n{e}", + self.tasks[index].value + ); } - + set_update = true; self.updated_task = String::new(); - + for item in &mut self.tasks { if item.already_exists { item.already_exists = false; @@ -187,14 +246,14 @@ impl Todo { if let Err(e) = db::delete_tasks(&self.conn, None) { eprintln!("[ERROR] Failed to delete all tasks:\n{e}") } - + self.new_task = String::from(""); self.updated_task = String::from(""); self.tasks.clear(); self.completed_tasks = 0; Task::none() - }, + } Message::Event(event) => match event { Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(key::Named::Tab), @@ -211,22 +270,20 @@ impl Todo { key: keyboard::Key::Named(key::Named::Delete), modifiers, .. - }) => { + }) => { if modifiers.control() { Task::perform(async move { Message::DeleteAll }, |result| result) } else { Task::none() } } - _ => Task::none() + _ => Task::none(), }, - Message::TaskPush(option_id) => { - match option_id { - Some(i) => Task::perform(async move { Message::EditTask(i) }, |result| result), - None => Task::perform(async { Message::AddTask }, |result| result), - } + Message::TaskPush(option_id) => match option_id { + Some(i) => Task::perform(async move { Message::EditTask(i) }, |result| result), + None => Task::perform(async { Message::AddTask }, |result| result), }, - Message::StorageToggle(toggle) => { + Message::StorageToggle(_toggle) => { // todo must be enabled eventually //self.local_storage = toggle; @@ -235,53 +292,56 @@ impl Todo { here we only call for storage change not implement the whole system as the system should be running since the program startup */ - + // also how do we even implement that Task::none() - }, + } Message::ToggleUnselect(toggle) => { self.select_all = toggle; for task in &mut self.tasks { task.checked = toggle; } - + if toggle { self.completed_tasks = self.tasks.len(); - } else { + } else { self.completed_tasks = 0; } - + if let Err(e) = db::update_unselect_tasks(&self.conn, toggle) { eprintln!("[ERROR] Failed to update un/select all operation in database:\n{e}"); } - + Task::none() - }, + } + Message::SelectedTaskList(task) => { + self.current_task_group = Some(task); + Task::none() + } + Message::AddTaskList => { + if self.task_list_input.is_empty() { return Task::none(); } + self.task_list.push(self.task_list_input.clone()); + self.task_list_state = combo_box::State::new(self.task_list.clone()); + self.task_list_input = String::from(""); + Task::none() + } } } - pub(crate) fn view(&self) -> Element { + pub(crate) fn view(&self) -> Element { let input = text_input("Enter new task", &self.new_task) .width(300) .size(25) - .on_input(|str| Message::ContentUpdated(true, str)) + .on_input(|str| Message::ContentUpdated(ContentType::NewTask, str)) .on_submit(Message::TaskPush(None)); - let add_btn = button( - Text::new("Add") - .size(19) - .center() - ) + let add_btn = button(Text::new("Add").size(19).center()) .width(Length::Shrink) .padding(10) .on_press(Message::AddTask); - let clear_btn = button( - Text::new("Clear") - .size(19) - .center() - ) + let clear_btn = button(Text::new("Clear").size(19).center()) .width(Length::Shrink) .padding(10) .style(button::danger) @@ -291,14 +351,16 @@ impl Todo { let mut saved_tasks = column![]; for (i, task) in self.tasks.iter().enumerate() { - let chk = checkbox("", task.checked).size(25).on_toggle(move |b| Message::CheckTask(b, i)); + let chk = checkbox("", task.checked) + .size(25) + .on_toggle(move |b| Message::CheckTask(b, i)); let mut label = Text::new(&task.value).width(200); if task.already_exists { label = label.style(danger); } let input = text_input("Enter new name", &self.updated_task) .width(200) - .on_input(|str| Message::ContentUpdated(false, str)) + .on_input(|str| Message::ContentUpdated(ContentType::ExistingTask, str)) .on_submit(Message::TaskPush(Some(i))); let mut edit = button(if task.edit { "save" } else { "edit" }).width(Length::Shrink); if task.can_update { @@ -309,29 +371,59 @@ impl Todo { delete = delete.on_press(Message::DeleteTask(i)); } let mut task_line = row![chk]; - task_line = if task.edit { task_line.push(input) } else { task_line.push(label) }; - task_line = task_line.push(edit).push(delete).spacing(10).padding([5, 10]); + task_line = if task.edit { + task_line.push(input) + } else { + task_line.push(label) + }; + task_line = task_line + .push(edit) + .push(delete) + .spacing(10) + .padding([5, 10]); saved_tasks = saved_tasks.push(task_line); } let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len())); - let unselect = checkbox("Un/select all", self.select_all).on_toggle(Message::ToggleUnselect); - let storage = checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle); - let footer = row![status, Space::with_width(Length::Fill), unselect, storage].padding(10).spacing(10); + let tasklist = combo_box( + &self.task_list_state, + "Test...", + self.current_task_group.as_ref(), + Message::SelectedTaskList, + ) + .on_input(|str| Message::ContentUpdated(ContentType::NewList, str)); + let add_tasklist = button("+").on_press(Message::AddTaskList); + let tasklist_group = row![tasklist.width(Length::Fill), add_tasklist].spacing(5); + let unselect = + checkbox("Select all", self.select_all).on_toggle(Message::ToggleUnselect); + let storage = + checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle); + let footer = row![ + status, + tasklist_group, + unselect, + storage + ] + .padding(10) + .spacing(10); let mut output = column![new_task.padding(10)]; - output = if self.tasks.is_empty() { output.push(saved_tasks.height(Length::Fill)) } else { output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) }; + output = if self.tasks.is_empty() { + output.push(saved_tasks.height(Length::Fill)) + } else { + output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) + }; output = output.align_x(Center).push(footer); center(output).into() } - pub(crate) fn theme(&self) -> Theme { + pub(crate) fn theme(&self) -> Theme { Theme::Dark } - pub(crate) fn subscription(&self) -> Subscription { + pub(crate) fn subscription(&self) -> Subscription { event::listen().map(Message::Event) } @@ -365,20 +457,21 @@ impl Todo { } } } - + fn duplicate_exists(&self, task_name: Option<&String>) -> bool { for task in &self.tasks { if task.already_exists { match task_name { Some(t) => { - if task.value != *t { return true; } - }, + if task.value != *t { + return true; + } + } None => return true, } - } } - + false } -} \ No newline at end of file +}