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
/.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 rusqlite::{params, Connection, Result};
use crate::def;
use crate::def::Task;
pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
// let conn = Connection::open_in_memory()?;
let conn = Connection::open(path)?;
conn.execute(
@ -16,32 +14,74 @@ pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
()
)?;
// todo
Ok(conn)
}
pub(crate) fn get_tasks(conn: &Connection) -> Result<()> {
let mut stmt = conn.prepare("SELECT checked, value FROM tasks")?;
let task_iter = stmt.query_map([], |row| {
Ok(Task {
checked: row.get(0)?,
value: row.get(1)?,
pub(crate) fn get_tasks(conn: &Connection) -> std::result::Result<Vec<TaskColumn>, Error> {
let mut stmt = conn.prepare("SELECT id, checked, value FROM tasks")?;
let tasks_iter = stmt.query_map([], |row| {
Ok(TaskColumn {
id: row.get(0)?,
checked: row.get(1)?,
value: row.get(2)?,
})
})?;
for task in task_iter {
println!("{:?}", task);
let mut tasks = Vec::new();
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(
"INSERT INTO tasks (checked, value) VALUES (?1, ?2)",
(&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(())
}

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

View File

@ -1,24 +1,75 @@
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 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 {
fn default() -> Self {
Todo::new()
// startup checks
}
}
impl Todo {
fn new() -> Self {
Self {
let mut init = Self {
new_task: String::new(),
updated_task: String::new(),
tasks: Vec::new(),
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> {
match message {
@ -34,31 +85,60 @@ impl Todo {
Message::AddTask => {
if self.new_task.is_empty() { 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) => {
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.new_task = String::new();
Task::none()
},
Message::DeleteTask(id) => {
if self.tasks[id].checked {
Message::DeleteTask(index) => {
if self.tasks[index].checked {
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()
},
}
Message::CheckTask(choice, id) => {
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].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(id) => {
@ -70,6 +150,16 @@ impl Todo {
}
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;
self.updated_task = String::new();
} else {
@ -82,6 +172,10 @@ impl Todo {
widget::focus_previous()
},
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.updated_task = String::from("");
self.tasks.clear();
@ -121,7 +215,8 @@ impl Todo {
}
},
Message::StorageToggle(toggle) => {
self.local_storage = toggle;
// todo must be enabled eventually
//self.local_storage = toggle;
/*
todo
@ -129,6 +224,26 @@ impl Todo {
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()
},
}
@ -186,8 +301,9 @@ impl Todo {
}
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), 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)];
output = if self.tasks.is_empty() { output.push(saved_tasks.height(Length::Fill)) } else { output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) };
@ -212,4 +328,25 @@ impl Todo {
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);
}
}
}
}