local storage - fin

This commit is contained in:
Martin Vrhovšek 2025-02-02 03:33:01 +01:00
parent f686c51611
commit cac9af75f2
5 changed files with 228 additions and 88 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/target /target
/.idea /.idea
/todo-db.db3 /todolist-db.db3

View File

@ -1,10 +1,8 @@
use crate::todo::{ChangeType, TaskColumn};
use rusqlite::{params, Connection, Error, Result};
use std::path::Path; use std::path::Path;
use rusqlite::{params, Connection, Result};
use crate::def;
use crate::def::Task;
pub(crate) fn startup(path: &Path) -> Result<(Connection)> { pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
// let conn = Connection::open_in_memory()?;
let conn = Connection::open(path)?; let conn = Connection::open(path)?;
conn.execute( conn.execute(
@ -16,32 +14,74 @@ pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
() ()
)?; )?;
// todo
Ok(conn) Ok(conn)
} }
pub(crate) fn get_tasks(conn: &Connection) -> Result<()> { pub(crate) fn get_tasks(conn: &Connection) -> std::result::Result<Vec<TaskColumn>, Error> {
let mut stmt = conn.prepare("SELECT checked, value FROM tasks")?; let mut stmt = conn.prepare("SELECT id, checked, value FROM tasks")?;
let task_iter = stmt.query_map([], |row| { let tasks_iter = stmt.query_map([], |row| {
Ok(Task { Ok(TaskColumn {
checked: row.get(0)?, id: row.get(0)?,
value: row.get(1)?, checked: row.get(1)?,
value: row.get(2)?,
}) })
})?; })?;
for task in task_iter { let mut tasks = Vec::new();
println!("{:?}", task); for task in tasks_iter {
tasks.push(task?);
} }
Ok(()) Ok(tasks)
} }
pub(crate) fn insert_task(task: Task, conn: &Connection) -> Result<()> { pub(crate) fn insert_task(conn: &Connection, task: TaskColumn) -> Result<i64> {
conn.execute( conn.execute(
"INSERT INTO tasks (checked, value) VALUES (?1, ?2)", "INSERT INTO tasks (checked, value) VALUES (?1, ?2)",
(&task.checked, &task.value), (&task.checked, &task.value),
)?; )?;
Ok(conn.last_insert_rowid())
}
// happens if we un/select task or we rename the task
pub(crate) fn update_task(conn: &Connection, task: TaskColumn, change: ChangeType) -> Result<()> {
let (query, params) = match change {
ChangeType::Checked => (
"UPDATE tasks SET checked = ?1 WHERE id = ?2",
params![&task.checked, &task.id]
),
ChangeType::Value => (
"UPDATE tasks SET value = ?1 WHERE id = ?2",
params![&task.value, &task.id]
),
};
conn.execute(query, params)?;
Ok(())
}
pub(crate) fn update_unselect_tasks(conn: &Connection, checked: bool) -> Result<()> {
conn.execute(
"UPDATE tasks SET checked = ?1",
(&checked,)
)?;
Ok(())
}
pub(crate) fn delete_tasks(conn: &Connection, id: Option<usize>) -> Result<()> {
let (query, params) = match id {
Some(t) => (
"DELETE FROM tasks WHERE id = ?1",
params![t.clone()]
),
None => (
"DELETE FROM tasks",
params![]
),
};
conn.execute(query, params)?;
Ok(()) Ok(())
} }

View File

@ -1,35 +0,0 @@
use iced::Event;
#[derive(Debug)]
pub struct Task {
pub(crate) checked: bool,
pub(crate) value: String,
}
pub struct TaskData {
pub(crate) checked: bool,
pub(crate) value: String,
pub(crate) edit: bool,
pub(crate) can_update: bool,
pub(crate) can_delete: bool,
}
pub struct Todo {
pub(crate) new_task: String,
pub(crate) updated_task: String,
pub(crate) tasks: Vec<TaskData>,
pub(crate) completed_tasks: usize,
pub(crate) local_storage: bool,
}
#[derive(Debug, Clone)]
pub enum Message {
AddTask,
CheckTask(bool, usize),
EditTask(usize),
DeleteTask(usize),
DeleteAll,
ContentUpdated(bool, String),
Event(Event),
TaskPush(Option<usize>),
StorageToggle(bool),
}

View File

@ -1,18 +1,14 @@
use std::path::Path;
use iced::window::Settings; use iced::window::Settings;
use iced::Size; use iced::Size;
use def::Todo; use todo::Todo;
use crate::db::startup;
use crate::def::Task;
mod todo; mod todo;
mod def;
mod db; mod db;
/*fn main() -> iced::Result { fn main() -> iced::Result {
let settings = Settings { let settings = Settings {
size: Size::new(500.0, 600.0), size: Size::new(500.0, 600.0),
resizable: false, //resizable: false,
..Settings::default() ..Settings::default()
}; };
@ -21,18 +17,20 @@ mod db;
.theme(Todo::theme) .theme(Todo::theme)
.window(settings) .window(settings)
.run() .run()
}*/ }
fn main() { /*fn main() {
let path: &Path = Path::new("./todo-db.db3"); let path: &Path = Path::new("./todolist-db.db3");
let conn = startup(path); let conn = startup(path);
let t = Task { let t = Task {
checked: true, checked: true,
value: String::from("Troll Ink"), value: String::from("JJ"),
}; };
let c = conn.unwrap(); let c = conn.unwrap();
/*let r = db::insert_task(t, &c); let r = db::insert_task(t, &c);
println!("{:?}", r);*/ println!("{:?}", r);
let rr = db::get_tasks(&c); let rr = db::get_tasks(&c);
}
let x = c.close();
}*/

View File

@ -1,26 +1,77 @@
use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme}; use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme};
use iced::keyboard::key; use iced::keyboard::key;
use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text}; use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text};
use crate::def::{Message, TaskData, Todo}; use rusqlite::Connection;
use crate::db;
#[derive(Debug)]
pub struct TaskColumn {
pub(crate) id: usize,
pub(crate) checked: bool,
pub(crate) value: String,
}
pub struct TaskData {
id: usize,
checked: bool,
value: String,
edit: bool,
can_update: bool,
can_delete: bool,
}
pub(crate) struct Todo {
new_task: String,
updated_task: String,
tasks: Vec<TaskData>,
completed_tasks: usize,
local_storage: bool,
conn: Connection,
select_all: bool,
}
#[derive(Debug, Clone)]
pub enum Message {
AddTask,
CheckTask(bool, usize),
EditTask(usize),
DeleteTask(usize),
DeleteAll,
ContentUpdated(bool, String),
Event(Event),
TaskPush(Option<usize>),
StorageToggle(bool),
ToggleUnselect(bool),
}
pub enum ChangeType {
Checked,
Value,
}
impl Default for Todo { impl Default for Todo {
fn default() -> Self { fn default() -> Self {
Todo::new() Todo::new()
// startup checks
} }
} }
impl Todo { impl Todo {
fn new() -> Self { fn new() -> Self {
Self { let mut init = Self {
new_task: String::new(), new_task: String::new(),
updated_task: String::new(), updated_task: String::new(),
tasks: Vec::new(), tasks: Vec::new(),
completed_tasks: 0, completed_tasks: 0,
local_storage: true, 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"),
select_all: false,
};
Self::load_data_from_db(&mut init);
init
} }
pub(crate) fn update(&mut self, message: Message) -> Task<Message> { pub(crate) fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::ContentUpdated(new, value) => { Message::ContentUpdated(new, value) => {
if new { if new {
@ -34,31 +85,60 @@ impl Todo {
Message::AddTask => { Message::AddTask => {
if self.new_task.is_empty() { return Task::none(); } if self.new_task.is_empty() { return Task::none(); }
let data = TaskData {
let result = db::insert_task(&self.conn, TaskColumn {
id: 0,
checked: false, checked: false,
value: self.new_task.to_string(), value: self.new_task.to_string(),
edit: false, });
can_update: true,
can_delete: true, match result {
}; Ok(t) => {
let data = TaskData {
id: t as usize,
checked: false,
value: self.new_task.to_string(),
edit: false,
can_update: true,
can_delete: true,
};
self.tasks.push(data);
},
Err(e) => eprintln!("[ERROR] Failed to insert new task into DB:\n{e}"),
}
self.tasks.push(data);
self.new_task = String::new(); self.new_task = String::new();
Task::none() Task::none()
}, },
Message::DeleteTask(id) => { Message::DeleteTask(index) => {
if self.tasks[id].checked { if self.tasks[index].checked {
self.completed_tasks -= 1; self.completed_tasks -= 1;
} }
self.tasks.remove(id);
if let Err(e) = db::delete_tasks(&self.conn, Some(self.tasks[index].id)) {
eprintln!("[ERROR] Failed to delete task '{}':\n{e}", self.tasks[index].value);
}
self.tasks.remove(index);
Task::none() Task::none()
}, }
Message::CheckTask(choice, id) => { 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; self.tasks[id].checked = choice;
let result = db::update_task(&self.conn, TaskColumn {
id: self.tasks[id].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() Task::none()
}, },
Message::EditTask(id) => { Message::EditTask(id) => {
@ -70,6 +150,16 @@ impl Todo {
} }
self.tasks[id].value = self.updated_task.clone(); self.tasks[id].value = self.updated_task.clone();
let result = db::update_task(&self.conn, TaskColumn {
id: self.tasks[id].id,
checked: self.tasks[id].checked,
value: self.tasks[id].value.to_string(),
}, ChangeType::Value);
if let Err(e) = result {
eprintln!("[ERROR] Failed to update task '{}' in local storage:\n{e}", self.tasks[id].value);
}
set_update = true; set_update = true;
self.updated_task = String::new(); self.updated_task = String::new();
} else { } else {
@ -82,6 +172,10 @@ impl Todo {
widget::focus_previous() widget::focus_previous()
}, },
Message::DeleteAll => { Message::DeleteAll => {
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.new_task = String::from("");
self.updated_task = String::from(""); self.updated_task = String::from("");
self.tasks.clear(); self.tasks.clear();
@ -121,20 +215,41 @@ impl Todo {
} }
}, },
Message::StorageToggle(toggle) => { Message::StorageToggle(toggle) => {
self.local_storage = toggle; // todo must be enabled eventually
//self.local_storage = toggle;
/* /*
todo todo
here we only call for storage change not implement the whole system here we only call for storage change not implement the whole system
as the system should be running since the program startup 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 {
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() Task::none()
}, },
} }
} }
pub(crate) fn view(&self) -> Element<Message> { pub(crate) fn view(&self) -> Element<Message> {
let input = text_input("Enter new task", &self.new_task) let input = text_input("Enter new task", &self.new_task)
.width(300) .width(300)
.size(25) .size(25)
@ -186,8 +301,9 @@ impl Todo {
} }
let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len())); 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 storage = checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle);
let footer = row![status, Space::with_width(Length::Fill), storage].padding(10); let footer = row![status, Space::with_width(Length::Fill), unselect, storage].padding(10).spacing(10);
let mut output = column![new_task.padding(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)) };
@ -196,11 +312,11 @@ impl Todo {
center(output).into() center(output).into()
} }
pub(crate) fn theme(&self) -> Theme { pub(crate) fn theme(&self) -> Theme {
Theme::Dark Theme::Dark
} }
pub(crate) fn subscription(&self) -> Subscription<Message> { pub(crate) fn subscription(&self) -> Subscription<Message> {
event::listen().map(Message::Event) event::listen().map(Message::Event)
} }
@ -212,4 +328,25 @@ impl Todo {
task.can_delete = enable; task.can_delete = enable;
} }
} }
fn load_data_from_db(&mut self) {
if let Ok(t) = db::get_tasks(&self.conn) {
for item in t {
let data = TaskData {
id: item.id,
checked: item.checked,
value: item.value,
edit: false,
can_update: true,
can_delete: true,
};
if item.checked {
self.completed_tasks += 1;
}
self.tasks.push(data);
}
}
}
} }