sync list updates with server

This commit is contained in:
Ide
2025-12-27 22:58:07 +01:00
parent 169c6f89cd
commit f413f27bf0
6 changed files with 144 additions and 35 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[package]
name = "inköpslista"
name = "inkopslista"
version = "0.0.0"
edition = "2024"
+99 -8
View File
@@ -1,10 +1,11 @@
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::error::Error;
use std::{error::Error, rc::Rc};
use inkopslista_lib::Item;
use slint::{ModelRc, ToSharedString, VecModel};
use inkopslista_lib::{Item, ItemData};
use reqwest::Url;
use slint::{Model, ModelRc, ToSharedString, VecModel};
slint::include_modules!();
@@ -21,29 +22,119 @@ async fn get_list() -> Result<Vec<Item>, MagicAnyError> {
Ok(list)
}
/// Add or update an item in the list
async fn put_list(item: &str, data: &ItemData) -> Result<(), MagicAnyError> {
let client = reqwest::Client::new();
let base = Url::parse("http://localhost:8000/api/list/")?;
let url = base.join(item)?;
let _response = client
.put(url)
.json(data)
.send()
.await?
.error_for_status()?;
Ok(())
}
async fn delete_item(item: &str) -> Result<(), MagicAnyError> {
let client = reqwest::Client::new();
let base = Url::parse("http://localhost:8000/api/list/")?;
let url = base.join(item)?;
let _response = client.delete(url).send().await?.error_for_status()?;
Ok(())
}
/// Convert shopping list to slint types
fn list_slint_convert(list: Vec<Item>) -> ModelRc<SItem> {
let list = list
fn update_slint_list(new_list: Vec<Item>, list: &VecModel<SItem>) {
list.clear();
new_list
.iter()
.map(|item| SItem {
name: item.name.to_shared_string(),
checked: item.data.checked,
})
.collect::<VecModel<_>>();
ModelRc::new(list)
.for_each(|item| list.push(item));
}
#[tokio::main]
async fn main() -> Result<(), MagicAnyError> {
let ui = App::new()?;
let state = ui.global::<State>();
let list = Rc::new(VecModel::default());
state.set_list(ModelRc::new(list.clone()));
// HACK: load shopping list from server on start
match get_list().await {
Ok(list) => ui.set_list(list_slint_convert(list)),
Ok(new_list) => update_slint_list(new_list, &list),
Err(e) => {
eprintln!("{e:?}");
}
}
let list2 = list.clone();
state.on_add_to_list(move |item| {
if list2.iter().any(|item2| item2.name == item) {
return;
// TODO: implement increment when number is added
}
list2.push(SItem {
checked: false,
name: item.clone(),
});
tokio::spawn(async move {
// TODO: add number to increment
let res = put_list(&item, &ItemData { checked: false }).await;
if let Err(err) = res {
eprintln!("{err:?}");
}
});
});
let list2 = list.clone();
state.on_check_item(move |item| {
let positem = list2
.iter()
.enumerate()
.find(|(_, item2)| item2.name == item);
let (pos, mut item) = match positem {
Some(positem) => positem,
None => return,
};
item.checked = !item.checked;
list2.set_row_data(pos, item.clone());
tokio::spawn(async move {
let res = put_list(
&item.name,
&ItemData {
checked: item.checked,
},
)
.await;
if let Err(err) = res {
eprintln!("{err:?}");
}
});
});
let list2 = list.clone();
state.on_delete_checked(move || {
let poss: Vec<_> = list2
.iter()
.enumerate()
.filter(|(_, item)| item.checked)
.map(|(pos, item)| (pos, item.name))
.collect();
for &(pos, _) in poss.iter().rev() {
list2.remove(pos);
}
tokio::spawn(async move {
for (_, name) in poss {
let res = delete_item(&name).await;
if let Err(err) = res {
eprintln!("{err:?}");
}
}
});
});
ui.run()?;
+30 -13
View File
@@ -16,6 +16,17 @@ export struct SItem {
checked: bool,
}
export global State {
in-out property <[SItem]> list: [
{ name: "Test 1", checked: true },
{ name: "Test 2", checked: false },
{ name: "Test 3", checked: false },
];
callback add-to-list(string);
callback check-item(string);
callback delete-checked();
}
component GoodListItem inherits Rectangle {
in-out property <SItem> item;
background: #ffffff10;
@@ -24,8 +35,9 @@ component GoodListItem inherits Rectangle {
HorizontalBox {
check := CheckBox {
checked: root.item.checked;
toggled => {
// TODO
toggled() => {
State.check-item(item.name);
debug("CheckBox checked: ", self.checked);
}
}
@@ -38,7 +50,8 @@ component GoodListItem inherits Rectangle {
input := TouchArea {
clicked => {
root.item.checked = !root.item.checked;
//root.item.checked = !root.item.checked;
State.check-item(item.name);
}
}
}
@@ -76,6 +89,9 @@ component GoodButtons inherits ScrollView {
width: root.button-width * 1pt;
height: root.button-height * 1pt;
text: item;
clicked => {
State.add-to-list(item);
}
}
}
@@ -96,9 +112,9 @@ component AddView inherits VerticalBox {
Button {
width: self.height;
text: "+";
// clicked => {
// goods.items[goods.items.length] = input.text;
// }
clicked => {
State.add-to-list(input.text);
}
}
}
@@ -114,27 +130,28 @@ component ShopView inherits VerticalBox {
Button {
text: "I am done hehe";
clicked => {
State.delete-checked();
}
}
}
export component App inherits Window {
in property <[SItem]> list: [
{ name: "Test 1", checked: true },
{ name: "Test 2", checked: false },
{ name: "Test 3", checked: false },
];
preferred-height: 700px;
preferred-width: 480px;
TabWidget {
Tab {
title: "Add";
AddView {
list <=> list;
list <=> State.list;
}
}
Tab {
title: "Shop";
ShopView {
list <=> list;
list <=> State.list;
}
}
}