Load shopping list from server on app startup

This commit is contained in:
2025-12-20 20:35:54 +01:00
parent 8059ab7b85
commit 22f829f579
10 changed files with 1503 additions and 1697 deletions
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
[workspace]
resolver = "3"
members = [
"inkopslista-lib",
"inköpslista-srv",
"inköpslista-gui",
]
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "inkopslista-lib"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
+13
View File
@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct Item {
pub name: String,
pub data: ItemData,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct ItemData {
pub checked: bool,
}
+3
View File
@@ -4,6 +4,9 @@ version = "0.0.0"
edition = "2024"
[dependencies]
reqwest = { version = "0.12.26", features = ["json"] }
inkopslista-lib = { path = "../inkopslista-lib" }
tokio = { version = "1.48.0", features = ["full"] }
[dependencies.slint]
version = "1.14.1"
+38 -1
View File
@@ -3,11 +3,48 @@
use std::error::Error;
use inkopslista_lib::Item;
use slint::{ModelRc, ToSharedString, VecModel};
slint::include_modules!();
fn main() -> Result<(), Box<dyn Error>> {
// TODO: use anyhow
type MagicAnyError = Box<dyn Error>;
/// GET "/api/list" from the server
async fn get_list() -> Result<Vec<Item>, MagicAnyError> {
// TODO: don't hardcode url
let list = reqwest::get("http://localhost:8000/api/list")
.await?
.json::<Vec<Item>>()
.await?;
Ok(list)
}
/// Convert shopping list to slint types
fn list_slint_convert(list: Vec<Item>) -> ModelRc<SItem> {
let list = list
.iter()
.map(|item| SItem {
name: item.name.to_shared_string(),
checked: item.data.checked,
})
.collect::<VecModel<_>>();
ModelRc::new(list)
}
#[tokio::main]
async fn main() -> Result<(), MagicAnyError> {
let ui = App::new()?;
// HACK: load shopping list from server on start
match get_list().await {
Ok(list) => ui.set_list(list_slint_convert(list)),
Err(e) => {
eprintln!("{e:?}");
}
}
ui.run()?;
Ok(())
+65 -25
View File
@@ -1,35 +1,57 @@
import { TabWidget, AboutSlint, Button, VerticalBox, ListView, CheckBox, ScrollView, HorizontalBox, TextEdit, GridBox } from "std-widgets.slint";
import {
TabWidget,
AboutSlint,
Button,
VerticalBox,
ListView,
CheckBox,
ScrollView,
HorizontalBox,
TextEdit,
GridBox,
} from "std-widgets.slint";
export struct SItem {
name: string,
checked: bool,
}
component GoodListItem inherits Rectangle {
in property <string> name: "Mjölk";
in-out property <bool> checked: false;
in-out property <SItem> item;
background: #ffffff10;
border-radius: 8pt;
border-width: 10pt;
HorizontalBox {
check := CheckBox {
checked <=> root.checked;
checked: root.item.checked;
toggled => {
// TODO
}
}
Text {
opacity: (checked ? 0.5 : 1.0);
opacity: (root.item.checked ? 0.5 : 1.0);
font-size: 12pt;
text: name;
text: root.item.name;
}
}
input := TouchArea {
clicked => {
checked = !checked;
root.item.checked = !root.item.checked;
}
}
}
component GoodsList {
in property <[string]> items: ["Mjölk", "Ost", "Ägg"];
in property <[SItem]> items;
ListView {
for item in items: VerticalLayout {
GoodListItem {
name: item;
item: item;
}
Rectangle {
height: 4pt;
}
@@ -49,53 +71,71 @@ component GoodButtons inherits ScrollView {
viewport-height: (count-y * (button-height + button-spacing)) * 1pt;
for item[idx] in favorites: Button {
x: floor(mod(idx, count-x)) * (root.button-width + root.button-spacing) * 1pt ;
y: floor(idx / count-x) * (root.button-height + root.button-spacing) * 1pt;
width: root.button-width * 1pt;
x: floor(mod(idx, count-x)) * (root.button-width + root.button-spacing) * 1pt;
y: floor(idx / count-x) * (root.button-height + root.button-spacing) * 1pt;
width: root.button-width * 1pt;
height: root.button-height * 1pt;
text: item;
}
}
component AddView inherits VerticalBox {
goods:= GoodsList {
in property <[SItem]> list;
goods := GoodsList {
items <=> list;
}
HorizontalLayout {
height: 50pt;
input:= TextEdit {
}
input := TextEdit { }
Rectangle {
width: 4pt; // spacing
}
Button {
width: self.height;
text: "+";
clicked => {
goods.items[goods.items.length] = input.text;
}
// clicked => {
// goods.items[goods.items.length] = input.text;
// }
}
}
GoodButtons {
}
GoodButtons { }
}
component ShopView inherits VerticalBox {
goods := GoodsList {}
in property <[SItem]> list;
goods := GoodsList {
items <=> list;
}
Button {
text: "I am done hehe";
}
}
export component App inherits Window {
in property <[SItem]> list: [
{ name: "Test 1", checked: true },
{ name: "Test 2", checked: false },
{ name: "Test 3", checked: false },
];
TabWidget {
Tab {
title: "Add";
AddView {}
}
AddView {
list <=> list;
}
}
Tab {
title: "Shop";
ShopView {}
}
ShopView {
list <=> list;
}
}
}
}
-1607
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -7,3 +7,4 @@ edition = "2021"
rocket = { version = "0.5.1", features = ["json"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
inkopslista-lib = { path = "../inkopslista-lib" }
+2 -13
View File
@@ -1,8 +1,9 @@
#[macro_use]
extern crate rocket;
use std::{ops::Deref, sync::Mutex};
use std::sync::Mutex;
use inkopslista_lib::{Item, ItemData};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};
@@ -59,15 +60,3 @@ fn rocket() -> _ {
struct MockDb {
pub list: Vec<Item>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
struct Item {
pub name: String,
pub data: ItemData,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
struct ItemData {
pub checked: bool,
}