From f16be1536ccedaf8d104e2210fa909bd85900157 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 8 Jun 2025 17:43:48 +0200 Subject: [PATCH] rm dioxus-sdk storage + localstorage --- Cargo.lock | 146 +---------- Cargo.toml | 3 +- src/api.rs | 37 +-- src/main.dart | 101 ------- src/main.rs | 10 +- src/pages/consume.dart | 115 -------- src/pages/expandable_list.dart | 60 ----- src/pages/flow/active_flow_page.dart | 144 ---------- src/pages/flow/create_flow_page.dart | 135 ---------- src/pages/flow/end_flow_page.dart | 96 ------- src/pages/flow/flow_info_page.dart | 85 ------ src/pages/flow/flow_note.dart | 58 ---- src/pages/flow/flows_page.dart | 175 ------------- src/pages/itemview.dart | 118 --------- src/pages/locations.dart | 165 ------------ src/pages/supply.dart | 354 ------------------------- src/pages/transaction.dart | 378 --------------------------- src/setup.rs | 10 +- src/store.rs | 24 ++ 19 files changed, 49 insertions(+), 2165 deletions(-) delete mode 100644 src/main.dart delete mode 100644 src/pages/consume.dart delete mode 100644 src/pages/expandable_list.dart delete mode 100644 src/pages/flow/active_flow_page.dart delete mode 100644 src/pages/flow/create_flow_page.dart delete mode 100644 src/pages/flow/end_flow_page.dart delete mode 100644 src/pages/flow/flow_info_page.dart delete mode 100644 src/pages/flow/flow_note.dart delete mode 100644 src/pages/flow/flows_page.dart delete mode 100644 src/pages/itemview.dart delete mode 100644 src/pages/locations.dart delete mode 100644 src/pages/supply.dart delete mode 100644 src/pages/transaction.dart create mode 100644 src/store.rs diff --git a/Cargo.lock b/Cargo.lock index e6ff005..1a0e798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,15 +206,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -395,7 +386,6 @@ dependencies = [ "chrono", "dioxus", "dioxus-material-icons", - "dioxus-sdk", "gloo-timers", "image", "log", @@ -483,12 +473,6 @@ dependencies = [ "half", ] -[[package]] -name = "cobs" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" - [[package]] name = "cocoa" version = "0.25.0" @@ -732,12 +716,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1289,31 +1267,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "dioxus-sdk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7b74aede7070ec1c0ef582dbec8bce93a5c40421155459fcdbcaa0e6ef0bf0" -dependencies = [ - "cfg-if", - "dioxus", - "dioxus-signals", - "directories", - "futures-util", - "js-sys", - "once_cell", - "postcard", - "rustc-hash", - "serde", - "tokio", - "tracing", - "uuid", - "warnings", - "wasm-bindgen", - "web-sys", - "yazi", -] - [[package]] name = "dioxus-signals" version = "0.6.3" @@ -1327,7 +1280,6 @@ dependencies = [ "once_cell", "parking_lot", "rustc-hash", - "serde", "tracing", "warnings", ] @@ -1376,33 +1328,13 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys 0.3.7", -] - [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", + "dirs-sys", ] [[package]] @@ -1413,7 +1345,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", + "redox_users", "windows-sys 0.59.0", ] @@ -1500,18 +1432,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -2247,15 +2167,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -2274,20 +2185,6 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version 0.4.1", - "serde", - "spin", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.4.1" @@ -3666,19 +3563,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" -[[package]] -name = "postcard" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "serde", -] - [[package]] name = "potential_utf" version = "0.1.2" @@ -3937,17 +3821,6 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "redox_users" version = "0.5.0" @@ -4583,15 +4456,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5984,12 +5848,6 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" -[[package]] -name = "yazi" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" - [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 67e8b3f..b1dee79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] dioxus = { version = "0.6.0", features = ["router"] } -web-sys = { version = "0.3", features = ["HtmlVideoElement", "MediaDevices", "MediaStream", "MediaStreamConstraints", "Navigator", "HtmlCanvasElement", "CanvasRenderingContext2d", "MediaStreamTrack", "ImageData"] } +web-sys = { version = "0.3", features = ["HtmlVideoElement", "MediaDevices", "MediaStream", "MediaStreamConstraints", "Navigator", "HtmlCanvasElement", "CanvasRenderingContext2d", "MediaStreamTrack", "ImageData", "Storage", "Window"] } wasm-bindgen = "0.2" log = "0.4.27" wasm-bindgen-futures = "0.4.50" @@ -18,7 +18,6 @@ gloo-timers = { version = "0.3.0", features = ["futures"] } base64 = "0.22.1" bardecoder = "0.5.0" serde_json = "1.0.140" -dioxus-sdk = { version = "0.6.0", features = ["storage"] } serde = { version = "1.0.219", features = ["derive"] } reqwest = { version = "0.12.15", features = ["json"] } dioxus-material-icons = "3.0.0" diff --git a/src/api.rs b/src/api.rs index 2ae61f3..67acb1b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use dioxus::signals::{Readable, Writable}; -use dioxus_sdk::storage::{get_from_storage, use_persistent, SessionStorage}; use serde_json::json; use crate::setup::Credentials; +use crate::store::{load_from_local_storage, save_to_local_storage}; use crate::try_recover_api; use reqwest::header::{HeaderMap, HeaderValue}; @@ -24,8 +24,7 @@ pub async fn api_get_auth(path: String) -> Result where T: DeserializeOwned, { - let creds = - get_from_storage::("creds".to_owned(), || Credentials::default()); + let creds = load_from_local_storage::("creds").unwrap_or_default(); let token = creds.token.as_str(); let instance = creds.instance_url.as_str(); @@ -52,8 +51,7 @@ where T: DeserializeOwned, X: Serialize, { - let creds = - get_from_storage::("creds".to_owned(), || Credentials::default()); + let creds = load_from_local_storage::("creds").unwrap_or_default(); let token = creds.token.as_str(); let instance = creds.instance_url.as_str(); @@ -94,13 +92,9 @@ impl API { let flow_info: HashMap = api_get_auth("/flows".to_string()).await.unwrap(); - let mut p_items = use_persistent("api_items", || Vec::::new()); - p_items.set(items.clone()); - let mut p_locations = - use_persistent("api_locations", || HashMap::::new()); - p_locations.set(locations.clone()); - let mut p_flowinfo = use_persistent("api_flow_info", || HashMap::::new()); - p_flowinfo.set(flow_info.clone()); + save_to_local_storage("api_items", items.clone()); + save_to_local_storage("api_locations", locations.clone()); + save_to_local_storage("api_flow_info", flow_info.clone()); Self { instance, @@ -112,19 +106,12 @@ impl API { pub fn try_recover() -> Self { Self { - instance: use_persistent("creds", || Credentials::default()) - .read() - .instance_url - .clone(), - items: use_persistent("api_items", || Vec::::new()) - .read() - .clone(), - locations: use_persistent("api_locations", || HashMap::::new()) - .read() - .clone(), - flow_info: use_persistent("api_flow_info", || HashMap::::new()) - .read() - .clone(), + instance: load_from_local_storage::("creds") + .unwrap_or_default() + .instance_url, + items: load_from_local_storage("api_items").unwrap_or_default(), + locations: load_from_local_storage("api_locations").unwrap_or_default(), + flow_info: load_from_local_storage("api_flow_info").unwrap_or_default(), } } diff --git a/src/main.dart b/src/main.dart deleted file mode 100644 index 2d6afb7..0000000 --- a/src/main.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/flow/flows_page.dart'; -import 'package:cdb_ui/pages/items.dart'; -import 'package:cdb_ui/pages/locations.dart'; -import 'package:cdb_ui/pages/setup.dart'; -import 'package:cdb_ui/pages/home.dart'; -import 'package:flutter/material.dart'; - -Future main() async { - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - bool init = false; - - refresh() { - setState(() {}); - } - - @override - void initState() { - super.initState(); - () async { - await API().init(refresh); - if (API().isInit()) { - await API().prefetch(); - setState(() { - init = true; - }); - } - }(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'CDB', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.deepPurple, brightness: Brightness.dark), - useMaterial3: true, - ), - home: API().isInit() - ? (API().isPrefetched() - ? const MyHomePage() - : const Scaffold( - body: Center(child: CircularProgressIndicator()), - )) - : const SetupPage()); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int pageIndex = 0; - - List pages = [ - const HomePage(), - const ItemsPage(), - const FlowsPage(), - const LocationsPage() - ]; - - @override - Widget build(BuildContext context) { - return Scaffold( - bottomNavigationBar: BottomNavigationBar( - fixedColor: Colors.white, - unselectedItemColor: Colors.white70, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"), - BottomNavigationBarItem( - icon: Icon(Icons.data_object), label: "Items"), - BottomNavigationBarItem(icon: Icon(Icons.receipt), label: "Flows"), - BottomNavigationBarItem( - icon: Icon(Icons.location_city), label: "Locations"), - ], - currentIndex: pageIndex, - onTap: (value) { - setState(() { - pageIndex = value; - }); - }, - ), - body: pages[pageIndex], - ); - } -} diff --git a/src/main.rs b/src/main.rs index ea361b1..8010ad4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use api::{Item, Transaction}; use dioxus::prelude::*; use dioxus_material_icons::{MaterialIcon, MaterialIconStylesheet}; -use dioxus_sdk::storage::{get_from_storage, use_persistent, SessionStorage}; use qrscan::QRCodeScanPage; use setup::{Credentials, SetupPage}; @@ -9,8 +8,10 @@ pub mod api; pub mod page; pub mod qrscan; pub mod setup; +pub mod store; use page::{supply::SupplyPageParam, *}; +use store::load_from_local_storage; #[derive(Debug, Clone, Routable, PartialEq)] #[rustfmt::skip] @@ -52,7 +53,6 @@ const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); pub static API: GlobalSignal> = Signal::global(|| None); fn main() { - dioxus_sdk::set_dir!(); dioxus::launch(App); } @@ -61,7 +61,7 @@ pub fn try_recover_api() -> crate::api::API { } pub async fn fetch_api() { - let res = get_from_storage::("creds".to_owned(), || Credentials::default()); + let res: Credentials = load_from_local_storage("creds").unwrap_or_default(); if !res.empty() { let api = api::API::new(res.instance_url.clone()).await; *crate::API.write() = Some(api); @@ -70,7 +70,7 @@ pub async fn fetch_api() { #[component] fn App() -> Element { - let creds = use_persistent("creds", || Credentials::default()); + let creds: Credentials = load_from_local_storage("creds").unwrap_or_default(); spawn(async move { fetch_api().await; @@ -82,7 +82,7 @@ fn App() -> Element { document::Link { rel: "stylesheet", href: TAILWIND_CSS } MaterialIconStylesheet { } - if creds.read().empty() { + if creds.empty() { SetupPage { } } else { Router:: {} diff --git a/src/pages/consume.dart b/src/pages/consume.dart deleted file mode 100644 index d3f03e2..0000000 --- a/src/pages/consume.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/supply.dart'; -import 'package:flutter/material.dart'; - -class ConsumePage extends StatefulWidget { - final Transaction transaction; - final Function refresh; - - const ConsumePage(this.transaction, this.refresh, {super.key}); - - @override - State createState() => _ConsumePageState(); -} - -class _ConsumePageState extends State { - final _formKey = GlobalKey(); - String _selectedDestination = ""; - String _price = ""; - - @override - void initState() { - super.initState(); - } - - void _consume() { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - - API() - .consumeItem(widget.transaction.uuid, _selectedDestination, - double.parse(_price)) - .then((_) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Item consumed successfully!')), - ); - Navigator.of(context).pop(); - widget.refresh(); - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Consume Item'), - ), - body: FutureBuilder( - future: API().getUniqueField( - widget.transaction.item, widget.transaction.variant, "destination"), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } - - var destinations = snapshot.data!; - - return Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Destination Field with Dropdown and Text Input - AutocompletedTextField( - options: destinations, - getValue: () => _selectedDestination, - onChanged: (value) { - _selectedDestination = value; - }, - onSelection: (selection) { - setState(() { - _selectedDestination = selection; - }); - }, - label: "Destination"), - - const SizedBox(height: 16), - - // Price Field - TextFormField( - decoration: const InputDecoration(labelText: 'Price'), - keyboardType: TextInputType.number, - initialValue: _price, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a price'; - } - if (double.tryParse(value) == null) { - return 'Please enter a valid number'; - } - return null; - }, - onSaved: (value) { - _price = value!; - }, - ), - - const SizedBox(height: 20), - - // Submit Button - ElevatedButton( - onPressed: _consume, - child: const Text('Consume Item'), - ), - ], - ), - ), - ); - }, - ), - ); - } -} diff --git a/src/pages/expandable_list.dart b/src/pages/expandable_list.dart deleted file mode 100644 index d8dd1eb..0000000 --- a/src/pages/expandable_list.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -class ExpandableListItem { - ExpandableListItem({ - required this.body, - required this.header, - this.isExpanded = false, - }); - - Widget body; - Widget header; - bool isExpanded; -} - -class ExpandableList extends StatefulWidget { - final List entries; - - const ExpandableList(this.entries, {super.key}); - - @override - State createState() => _ExpandableListState(); -} - -class _ExpandableListState extends State { - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Container( - child: _buildPanel(), - ), - ); - } - - Widget _buildPanel() { - return ExpansionPanelList( - expansionCallback: (int index, bool isExpanded) { - setState(() { - widget.entries[index].isExpanded = isExpanded; - }); - }, - children: widget.entries.map((ExpandableListItem item) { - return ExpansionPanel( - headerBuilder: (BuildContext context, bool isExpanded) { - return ListTile( - title: item.header, - onTap: () { - setState(() { - widget.entries.firstWhere((x) => x == item).isExpanded = - !isExpanded; - }); - }, - ); - }, - body: item.body, - isExpanded: item.isExpanded, - ); - }).toList(), - ); - } -} diff --git a/src/pages/flow/active_flow_page.dart b/src/pages/flow/active_flow_page.dart deleted file mode 100644 index d43b74e..0000000 --- a/src/pages/flow/active_flow_page.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'package:cdb_ui/api.dart' as API; -import 'package:cdb_ui/pages/flow/create_flow_page.dart'; -import 'package:cdb_ui/pages/flow/end_flow_page.dart'; -import 'package:cdb_ui/pages/flow/flow_note.dart'; -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; -import 'package:qr_bar_code/qr/src/qr_code.dart'; -import 'package:qr_bar_code/qr/src/types.dart'; - -class ActiveFlowPage extends StatefulWidget { - final API.Flow flow; - final API.FlowInfo info; - - const ActiveFlowPage(this.flow, this.info, {super.key}); - - @override - State createState() => _ActiveFlowPageState(); -} - -class _ActiveFlowPageState extends State { - _refresh() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.info.name), - actions: [ - IconButton( - onPressed: () async { - if (widget.info.produces?.isNotEmpty ?? false) { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - EndFlowWithProduce(widget.flow, widget.info))); - return; - } - - // simple dialog - var confirm = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Are you sure?'), - content: const Text('This will end the flow.'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () => Navigator.of(context).pop(true), - child: const Text('End'), - ), - ], - ), - ); - - if (confirm ?? false) { - await API - .API() - .endFlow(widget.flow.id) - .then((x) => Navigator.of(context).pop()); - } - }, - icon: const Icon(Icons.stop)), - if (widget.info.next != null) - IconButton( - onPressed: () { - var newInfo = API.API().getFlowInfo(widget.info.next!); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return CreateFlowPage( - newInfo, - () {}, - previousFlow: widget.flow, - ); - }, - )); - }, - icon: const Icon(Icons.arrow_forward)), - ], - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column( - children: [ - Row( - children: [ - QRCode( - data: widget.flow.id, - size: 128, - eyeStyle: const QREyeStyle(color: Colors.white), - dataModuleStyle: const QRDataModuleStyle(color: Colors.white), - semanticsLabel: "Transaction UUID", - ), - const SizedBox( - width: 16.0, - ), - Text("Started since: ${tsFormat(widget.flow.started)}"), - ], - ), - if (widget.flow.input != null) - ...widget.flow.input!.map((x) => Text("Input: $x")).toList(), - if (widget.flow.done != null) ...[ - Text("Ended: ${tsFormat(widget.flow.done!.ended)}"), - if (widget.flow.done!.next != null) - Text("Next: ${widget.flow.done!.next!}"), - ...widget.flow.done!.produced! - .map((x) => Text("Produced: $x")) - .toList(), - ], - const Divider(), - FutureBuilder( - future: API.API().getNotesOfFlow(widget.flow.id), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - - var data = snapshot.data!; - - return Expanded( - child: ListView( - children: data - .map( - (x) => FlowNoteCard(x), - ) - .toList())); - }, - ) - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => AddNotePage(widget.flow, _refresh))); - }, - child: const Icon(Icons.note_add), - ), - ); - } -} diff --git a/src/pages/flow/create_flow_page.dart b/src/pages/flow/create_flow_page.dart deleted file mode 100644 index 35d5fb2..0000000 --- a/src/pages/flow/create_flow_page.dart +++ /dev/null @@ -1,135 +0,0 @@ -import 'package:cdb_ui/api.dart' as API; -import 'package:cdb_ui/pages/flow/active_flow_page.dart'; -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; - -class CreateFlowPage extends StatefulWidget { - final API.FlowInfo info; - final Function refresh; - final API.Flow? previousFlow; - - const CreateFlowPage(this.info, this.refresh, {this.previousFlow, super.key}); - - @override - State createState() => _CreateFlowPageState(); -} - -class _CreateFlowPageState extends State { - List depends = []; - - void _create(BuildContext context) { - if (widget.previousFlow != null) { - API - .API() - .continueFlow(widget.previousFlow!.id, - input: depends.map((x) => x.uuid).toList()) - .then((x) { - API.API().getFlow(x).then((flow) { - var info = API.API().getFlowInfo(flow.kind); - Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (context) => ActiveFlowPage(flow, info), - )); - }); - }); - return; - } - - API - .API() - .startFlow(widget.info.id, input: depends.map((x) => x.uuid).toList()) - .then((flowID) { - widget.refresh(); - API.API().getFlow(flowID).then((flow) { - Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (context) => ActiveFlowPage(flow, widget.info))); - }); - }); - } - - void selectDependItems(BuildContext context, String itemVariant) { - var (item, variant) = API.itemVariant(itemVariant); - - API.API().getInventoryOfVariant(item, variant).then((transactions) { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return TransactionSelectPage(transactions, onSelect: (t) { - if (!depends.contains(t)) { - setState(() { - depends.add(t); - }); - } - }, exclude: depends); - }, - )); - }); - } - - List buildInputSelection(BuildContext context) { - if (widget.info.depends.isEmpty) { - return []; - } - - return [ - Column( - children: widget.info.depends.map((x) { - var (item, variant) = API.itemVariant(x); - return ElevatedButton( - onPressed: () { - selectDependItems(context, x); - }, - child: - Text("Add ${API.API().getItem(item).variants[variant]!.name}")); - }).toList()), - const Divider(), - ]; - } - - Widget flowContinuation() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(API.API().getFlowInfo(widget.previousFlow!.kind).name), - ), - ), - const Card(child: Icon(Icons.arrow_right)), - Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(widget.info.name), - ), - ) - ], - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.previousFlow != null - ? "Continue to ${widget.previousFlow!.kind}" - : "Create new ${widget.info.name} Flow")), - body: Column( - children: [ - if (widget.previousFlow != null) ...[ - flowContinuation(), - const Divider() - ], - ...buildInputSelection(context), - Card( - child: Column( - children: depends - .map((x) => TransactionCard(x, () {}, - onTap: (x) {}, onLongPress: (x) {})) - .toList())), - ElevatedButton( - onPressed: () => _create(context), - child: const Text("Create Flow")) - ], - ), - ); - } -} diff --git a/src/pages/flow/end_flow_page.dart b/src/pages/flow/end_flow_page.dart deleted file mode 100644 index 32f233d..0000000 --- a/src/pages/flow/end_flow_page.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:cdb_ui/api.dart' as API; -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/supply.dart'; -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; - -class EndFlowWithProduce extends StatefulWidget { - final API.Flow flow; - final API.FlowInfo info; - - const EndFlowWithProduce(this.flow, this.info, {super.key}); - - @override - State createState() => _EndFlowWithProduceState(); -} - -class _EndFlowWithProduceState extends State { - List produces = []; - late Map locations; - - @override - void initState() { - super.initState(); - locations = API.API().getLocations(); - } - - refresh() { - setState(() {}); - } - - void addProduced(SupplyForm t) { - setState(() { - produces.add(t); - }); - } - - List addProduceButtons() { - List ret = []; - - for (var i in widget.info.produces!) { - var (itemID, variant) = API.itemVariant(i); - var item = API.API().getItem(itemID); - ret.add(ElevatedButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return SupplyPage( - item, - refresh, - onlyVariants: [variant], - forcePrice: "0.00", - forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}", - onCreate: addProduced, - ); - }, - )); - }, - child: Text("Produced ${item.variants[variant]!.name}"))); - } - - return ret; - } - - _endFlow() { - API.API().endFlow(widget.flow.id, produced: produces).then((x) { - Navigator.of(context).pop(); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("End ${widget.info.name} Flow"), - ), - body: Column( - children: [ - ...addProduceButtons(), - const Divider(), - ...produces.map((x) { - return TransactionCard( - x.transaction(locations), - () {}, - onLongPress: (x) {}, - onTap: (x) {}, - ); - }).toList(), - const SizedBox( - height: 10, - ), - ElevatedButton(onPressed: _endFlow, child: const Text("End Flow")) - ], - ), - ); - } -} diff --git a/src/pages/flow/flow_info_page.dart b/src/pages/flow/flow_info_page.dart deleted file mode 100644 index 7ba14e7..0000000 --- a/src/pages/flow/flow_info_page.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:cdb_ui/api.dart' as API; -import 'package:cdb_ui/pages/flow/active_flow_page.dart'; -import 'package:cdb_ui/pages/flow/create_flow_page.dart'; -import 'package:flutter/material.dart'; - -class FlowInfoPage extends StatefulWidget { - final API.FlowInfo info; - - const FlowInfoPage(this.info, {super.key}); - - @override - State createState() => _FlowInfoPageState(); -} - -class _FlowInfoPageState extends State { - void refresh() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(widget.info.name)), - body: Column( - children: [ - // todo : ui improve - if (widget.info.next != null) - Text("Next: ${API.API().getFlowInfo(widget.info.next!).name}"), - if (widget.info.depends.isNotEmpty) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Text("Flow can use: "), - ...widget.info.depends.map((x) { - var (item, variant) = API.itemVariant(x); - return Text(API.API().getItem(item).variants[variant]!.name); - }).toList(), - ], - ), - if (widget.info.produces?.isNotEmpty ?? false) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Text("Flow can produce: "), - ...widget.info.produces!.map((x) { - var (item, variant) = API.itemVariant(x); - return Text(API.API().getItem(item).variants[variant]!.name); - }).toList(), - ], - ), - const Divider(), - FutureBuilder( - future: API.API().getActiveFlowsOf(widget.info.id), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - - var data = snapshot.data!; - - return Expanded( - child: ListView( - children: data - .map((x) => ListTile( - title: Text(x.id), - onTap: () => - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - ActiveFlowPage(x, widget.info), - )))) - .toList()), - ); - }, - ) - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => CreateFlowPage(widget.info, refresh))); - }, - child: const Icon(Icons.add)), - ); - } -} diff --git a/src/pages/flow/flow_note.dart b/src/pages/flow/flow_note.dart deleted file mode 100644 index 797aeab..0000000 --- a/src/pages/flow/flow_note.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; -import 'package:cdb_ui/api.dart' as API; - -class AddNotePage extends StatelessWidget { - late final TextEditingController _noteController = TextEditingController(); - final API.Flow flow; - final Function refresh; - - AddNotePage(this.flow, this.refresh, {super.key}); - - void _submit(BuildContext context) { - API.API().addNoteToFlow(flow.id, _noteController.text).then((x) { - refresh(); - Navigator.of(context).pop(); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Add Note"), - ), - body: Column( - children: [ - TextFormField( - decoration: const InputDecoration(labelText: 'Note'), - controller: _noteController, - maxLines: 10), - const SizedBox( - height: 14, - ), - ElevatedButton( - onPressed: () => _submit(context), child: const Text("Add Note")) - ], - ), - ); - } -} - -class FlowNoteCard extends StatelessWidget { - final API.FlowNote note; - - const FlowNoteCard(this.note, {super.key}); - - @override - Widget build(BuildContext context) { - return Card( - child: ListTile( - dense: true, - title: Text(tsFormat(note.timestamp), - style: const TextStyle(fontSize: 12)), - subtitle: Text(note.content, overflow: TextOverflow.ellipsis), - ), - ); - } -} diff --git a/src/pages/flow/flows_page.dart b/src/pages/flow/flows_page.dart deleted file mode 100644 index f172bea..0000000 --- a/src/pages/flow/flows_page.dart +++ /dev/null @@ -1,175 +0,0 @@ -import 'package:cdb_ui/api.dart' as API; -import 'package:cdb_ui/pages/expandable_list.dart'; -import 'package:cdb_ui/pages/flow/active_flow_page.dart'; -import 'package:cdb_ui/pages/flow/flow_info_page.dart'; -import 'package:cdb_ui/pages/supply.dart'; -import 'package:flutter/material.dart'; - -class FlowsPage extends StatefulWidget { - const FlowsPage({super.key}); - - @override - State createState() => _FlowsPageState(); -} - -class _FlowsPageState extends State { - int tabSelection = 0; - Map? flowInfos; - - @override - void initState() { - super.initState(); - flowInfos = API.API().getFlows(); - } - - Widget flowTile(BuildContext context, API.FlowInfo x) { - return ListTile( - title: Text(x.name), - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => FlowInfoPage(x), - )); - }, - ); - } - - Widget listFlowInfoByActive( - BuildContext context, Map infos) { - return FutureBuilder(future: () async { - var included = []; - - for (var key in infos.keys) { - var active = await API.API().getActiveFlowsOf(key); - if (active.isNotEmpty) { - included.add(infos[key]); - } - } - - return included; - }(), builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - - var included = snapshot.data!; - - return ListView( - children: included.map((x) => flowTile(context, x)).toList()); - }); - } - - Widget listAllFlowInfos( - BuildContext context, Map infos) { - return ListView( - children: infos.values.map((x) { - return flowTile(context, x); - }).toList()); - } - - Widget listFlowInfoByProduced( - BuildContext context, Map infos) { - Map> producedMapping = {}; - - for (var f in infos.values) { - for (var produces in f.produces ?? []) { - var item = API.itemVariant(produces).$1; - producedMapping.putIfAbsent(item, () { - return []; - }); - producedMapping[item]!.add(f); - } - } - - List items = []; - - for (var key in producedMapping.keys) { - var flows = Column( - children: producedMapping[key]!.map((x) { - return flowTile(context, x); - }).toList()); - items.add(ExpandableListItem( - body: flows, header: Text(API.API().getItem(key).name))); - } - - return ExpandableList(items); - } - - Widget listFlowInfoByDependant( - BuildContext context, Map infos) { - Map> dependsMapping = {}; - - for (var f in infos.values) { - for (var produces in f.depends) { - var item = API.itemVariant(produces).$1; - // todo : add only if item is in inventory - dependsMapping.putIfAbsent(item, () { - return []; - }); - dependsMapping[item]!.add(f); - } - } - - List items = []; - - for (var key in dependsMapping.keys) { - var flows = Column( - children: dependsMapping[key]!.map((x) { - return flowTile(context, x); - }).toList()); - items.add(ExpandableListItem( - body: flows, header: Text(API.API().getItem(key).name))); - } - - return ExpandableList(items); - } - - @override - Widget build(BuildContext context) { - if (flowInfos == null) { - return const CircularProgressIndicator(); - } - - return DefaultTabController( - length: 4, - child: Scaffold( - appBar: AppBar( - title: const Text("Flows"), - bottom: TabBar( - tabs: const [ - Tab(text: "All"), - Tab(text: "Produces"), - Tab(text: "Depends"), - Tab(text: "Active") - ], - onTap: (value) { - setState(() { - tabSelection = value; - }); - }, - )), - body: switch (tabSelection) { - 0 => listAllFlowInfos(context, flowInfos!), - 1 => listFlowInfoByProduced(context, flowInfos!), - 2 => listFlowInfoByDependant(context, flowInfos!), - 3 => listFlowInfoByActive(context, flowInfos!), - _ => const Text("..."), - }, - floatingActionButton: FloatingActionButton( - onPressed: () async { - // scan flow code - var code = await scanQRCode(context, title: "Scan Flow Code"); - - API.API().getFlow(code!).then((flow) { - var info = API.API().getFlowInfo(flow.kind); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return ActiveFlowPage(flow, info); - }, - )); - }); - }, - child: const Icon(Icons.qr_code), - ), - )); - } -} diff --git a/src/pages/itemview.dart b/src/pages/itemview.dart deleted file mode 100644 index 6d8d367..0000000 --- a/src/pages/itemview.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/home.dart'; -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; -import 'supply.dart'; - -// todo : show est. time remaining until inventory gets empty (based on demand) - -class ItemView extends StatefulWidget { - final Item item; - - @override - State createState() => _ItemViewState(); - - const ItemView({super.key, required this.item}); -} - -class _ItemViewState extends State { - void refresh() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(widget.item.name)), - body: Column(children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Align( - alignment: Alignment.centerLeft, - child: widget.item.image != null - ? Image.network( - "${API().instance}/${widget.item.image}", - height: 100, - width: 100, - ) - : null, - ), - const SizedBox( - width: 16.0, - ), - Column( - children: [ - Text( - widget.item.name, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - if (widget.item.category != null) - Text(widget.item.category!), - ], - ) - ], - ), - const SizedBox(height: 18), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: widget.item.variants.entries.map((entry) { - return Column(children: [ - Text( - entry.value.name, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - FutureBuilder( - future: API().getStat(widget.item.id, entry.key), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - - var stat = snapshot.data!; - - return Column( - children: [ - Text("Amount: ${stat.amount}"), - Text( - "Total Cost: ${stat.totalPrice.toStringAsFixed(2)}") - ], - ); - }, - ) - ]); - }).toList()), - const SizedBox( - height: 12, - ) - ], - ), - ), - FutureBuilder( - future: API().getInventory(widget.item.id), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - var data = snapshot.data!; - - return Expanded( - child: ListView( - children: - data.map((x) => TransactionCard(x, refresh)).toList())); - }, - ) - ]), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => SupplyPage(widget.item, refresh))); - }, - child: const Icon(Icons.add)), - ); - } -} diff --git a/src/pages/locations.dart b/src/pages/locations.dart deleted file mode 100644 index 5e62a79..0000000 --- a/src/pages/locations.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/supply.dart'; -import 'package:cdb_ui/pages/transaction.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_simple_treeview/flutter_simple_treeview.dart'; - -class LocationsPage extends StatefulWidget { - const LocationsPage({super.key}); - - @override - State createState() => _LocationsPageState(); -} - -class _LocationsPageState extends State { - Map? locations; - - @override - void initState() { - super.initState(); - locations = API().getLocations(); - } - - TreeNode buildTree(BuildContext context, String locID) { - return TreeNode( - key: ValueKey(locID), - content: Expanded( - child: ListTile( - title: Text(locations![locID]!.name), - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => LocationView(locations![locID]!), - )); - }, - ), - ), - children: locations!.keys.where((key2) { - if (locations![key2]!.parent != null) { - return locations![key2]!.parent! == locations![locID]!.id; - } - return false; - }).map((key2) { - return buildTree(context, key2); - }).toList()); - } - - @override - Widget build(BuildContext context) { - if (locations == null) { - return const Scaffold( - body: CircularProgressIndicator(), - ); - } - - return Scaffold( - appBar: AppBar( - title: const Text("Locations"), - ), - body: TreeView( - indent: 15, - nodes: locations!.keys - .where((key) => locations![key]!.parent == null) - .map((key) { - return buildTree(context, key); - }).toList()), - floatingActionButton: FloatingActionButton( - onPressed: () async { - // scan location code - var code = await scanQRCode(context, title: "Scan Location Code"); - - if (!locations!.containsKey(code)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('The location $code does not exist.')), - ); - return; - } - - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => LocationView(locations![code]!), - )); - }, - child: const Icon(Icons.qr_code), - ), - ); - } -} - -class LocationView extends StatefulWidget { - final Location location; - - const LocationView(this.location, {super.key}); - - @override - State createState() => _LocationViewState(); -} - -class _LocationViewState extends State { - bool recursive = true; - - void refresh() { - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.location.name), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Column( - children: [ - Card( - child: Column( - children: [ - if (widget.location.parent != null) - Text("Inside: ${widget.location.parent!}"), - if (widget.location.conditions?.temperature != null) - Text( - "Temperature: ${widget.location.conditions!.temperature} C°") - ], - ), - ), - Row( - children: [ - Checkbox( - value: !recursive, - onChanged: (bool? newValue) { - setState(() { - recursive = !(newValue ?? false); - }); - }, - ), - const Expanded( - child: Text( - 'Show only exact matches with location', - style: TextStyle(fontSize: 16), - ), - ), - ], - ), - Expanded( - child: FutureBuilder( - future: API().getTransactionsOfLocation(widget.location.id, - recursive: recursive), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const CircularProgressIndicator(); - } - - var data = snapshot.data!; - - return ListView( - children: data - .map((x) => TransactionCard(x, refresh)) - .toList()); - }, - ), - ) - ], - ), - ), - ); - } -} diff --git a/src/pages/supply.dart b/src/pages/supply.dart deleted file mode 100644 index 792b766..0000000 --- a/src/pages/supply.dart +++ /dev/null @@ -1,354 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:flutter/material.dart'; -import 'package:simple_barcode_scanner/enum.dart'; -import 'package:simple_barcode_scanner/simple_barcode_scanner.dart'; - -Future scanQRCode(BuildContext context, - {String title = "Scan QR Code"}) async { - var res = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SimpleBarcodeScannerPage( - scanType: ScanType.qr, - appBarTitle: title, - ), - )); - - return res; -} - -class SupplyPage extends StatefulWidget { - final Item item; - final Function refresh; - final List? onlyVariants; - final String? forcePrice; - final String? forceOrigin; - - // callback function for receiving a transaction without creating on the API - final Function(SupplyForm)? onCreate; - - const SupplyPage(this.item, this.refresh, - {this.onlyVariants, - this.onCreate, - this.forceOrigin, - this.forcePrice, - super.key}); - - @override - State createState() => _SupplyPageState(); -} - -class _SupplyPageState extends State { - late List availableVariants; - late String variant; - final _formKey = GlobalKey(); - String _selectedOrigin = ""; - String _selectedLocation = ""; - late TextEditingController _priceController; - late TextEditingController _noteController; - - @override - void initState() { - super.initState(); - availableVariants = - widget.onlyVariants ?? widget.item.variants.keys.toList(); - - variant = availableVariants.first; - - _selectedOrigin = widget.forceOrigin ?? ""; - _priceController = TextEditingController(text: widget.forcePrice ?? ""); - _noteController = TextEditingController(text: ""); - } - - void _supply() { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - - if (widget.onCreate != null) { - var t = SupplyForm( - itemID: widget.item.id, - variant: variant, - price: double.parse(_priceController.text), - origin: _selectedOrigin, - location: _selectedLocation, - note: _noteController.text); - - widget.onCreate!(t); - - Navigator.of(context).pop(); - widget.refresh(); - return; - } - - API() - .supplyItem( - widget.item.id, - variant, - double.parse(_priceController.text), - _selectedOrigin, - _selectedLocation, - _noteController.text) - .then((_) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Item added successfully!')), - ); - Navigator.of(context).pop(); - widget.refresh(); - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Add New Item'), - ), - body: FutureBuilder( - future: _fetchData(), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } - - var data = snapshot.data as Map; - var locations = data['locations']! as Map; - var origins = data['origins']! as List; - - return Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Variant Selection - DropdownButtonFormField( - hint: const Text('Select Variant'), - value: variant, - onChanged: (value) { - setState(() { - variant = value!; - }); - }, - items: availableVariants.map((entryKey) { - var entry = widget.item.variants[entryKey]!; - return DropdownMenuItem( - value: entryKey, - child: Text(entry.name), - ); - }).toList(), - onSaved: (value) { - variant = value!; - }, - ), - - const SizedBox(height: 16), - - // Origin Field with Dropdown and Text Input - if (widget.forceOrigin == null) ...[ - AutocompletedTextField( - options: origins, - getValue: () => _selectedOrigin, - onChanged: (value) { - _selectedOrigin = value; - }, - onSelection: (String selection) async { - var price = _priceController.text.isEmpty - ? await API() - .getLatestPrice(widget.item.id, variant, - origin: selection) - .then((x) => x.toStringAsFixed(2)) - : _priceController.text; - setState(() { - _priceController.text = price; - _selectedOrigin = selection; - }); - }, - label: "Origin"), - const SizedBox(height: 16), - ], - - // Price Field - if (widget.forcePrice == null) ...[ - TextFormField( - decoration: const InputDecoration(labelText: 'Price'), - keyboardType: TextInputType.number, - controller: _priceController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a price'; - } - if (double.tryParse(value) == null) { - return 'Please enter a valid number'; - } - return null; - }, - ), - const SizedBox(height: 16), - ], - - // Location Dropdown - Row( - children: [ - Expanded( - child: DropdownButtonFormField( - hint: const Text('Select Location'), - value: _selectedLocation, - onChanged: (value) { - setState(() { - _selectedLocation = value!; - }); - }, - items: locations.keys - .map>((id) { - return DropdownMenuItem( - value: id, - child: - Text(locations[id]!.fullNamePath(locations)), - ); - }).toList(), - onSaved: (value) { - _selectedLocation = value!; - }, - ), - ), - const SizedBox( - width: 12, - ), - IconButton( - onPressed: () async { - var code = await scanQRCode(context); - setState(() { - if (API().getLocations().keys.contains(code)) { - _selectedLocation = code!; - } - }); - }, - icon: const Icon(Icons.qr_code), - ), - ], - ), - - // Note - TextFormField( - decoration: const InputDecoration(labelText: 'Note'), - controller: _noteController, - maxLines: 5), - - const SizedBox(height: 20), - - // Submit Button - ElevatedButton( - onPressed: _supply, - child: const Text('Add Item'), - ), - ], - ), - ), - ); - }, - ), - ); - } - - Future> _fetchData() async { - var locations = API().getLocations(); - var origins = await API().getUniqueField(widget.item.id, variant, "origin"); - origins.insert(0, ""); - locations[""] = Location.zero(); - return {'locations': locations, 'origins': origins}; - } -} - -// ignore: must_be_immutable -class AutocompletedTextField extends StatelessWidget { - late List options; - late Function(String) onChanged; - late String Function() getValue; - late Function(String) onSelection; - late String label; - - AutocompletedTextField( - {super.key, - required this.options, - required this.getValue, - required this.onChanged, - required this.onSelection, - required this.label}); - - @override - Widget build(BuildContext context) { - return Autocomplete( - optionsBuilder: (TextEditingValue textEditingValue) { - if (textEditingValue.text.isEmpty) { - return options; - } - return options.where((String option) { - return option - .toLowerCase() - .contains(textEditingValue.text.toLowerCase()); - }); - }, - onSelected: onSelection, - fieldViewBuilder: - (context, textEditingController, focusNode, onFieldSubmitted) { - textEditingController.text = getValue(); - return TextFormField( - onChanged: onChanged, - controller: textEditingController, - focusNode: focusNode, - decoration: InputDecoration( - labelText: label, - border: const OutlineInputBorder(), - ), - ); - }, - ); - } -} - -class SupplyForm { - final String itemID; - final String variant; - final double price; - final String? origin; - final String? location; - final String note; - - factory SupplyForm.fromJson(Map json) { - return SupplyForm( - itemID: json['item'], - variant: json['variant'], - price: json['price'], - origin: json['origin'], - location: json['location'], - note: json['note'], - ); - } - - Map json() { - return { - "item": itemID, - "variant": variant, - "price": price, - "origin": origin, - "location": location, - "note": note - }; - } - - SupplyForm({ - required this.itemID, - required this.variant, - required this.price, - required this.origin, - required this.location, - required this.note, - }); - - Transaction transaction(Map locations) { - return Transaction.inMemory(itemID, variant, price, origin, - location != null ? locations[location!] : null, note); - } -} diff --git a/src/pages/transaction.dart b/src/pages/transaction.dart deleted file mode 100644 index 79e674d..0000000 --- a/src/pages/transaction.dart +++ /dev/null @@ -1,378 +0,0 @@ -import 'package:cdb_ui/api.dart'; -import 'package:cdb_ui/pages/consume.dart'; -import 'package:cdb_ui/pages/supply.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:qr_bar_code/qr/qr.dart'; - -class TransactionPage extends StatefulWidget { - final Transaction transaction; - final Function? refresh; - - const TransactionPage(this.transaction, {this.refresh, super.key}); - - @override - State createState() => _TransactionPageState(); -} - -class _TransactionPageState extends State { - late Transaction transaction; - - @override - void initState() { - super.initState(); - transaction = widget.transaction; - } - - Future reload() async { - if (widget.refresh != null) { - widget.refresh!(); - } - - var updateTransaction = await API().getTransaction(transaction.uuid); - - setState(() { - transaction = updateTransaction; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(API().getItem(transaction.item).name), - actions: [ - IconButton( - onPressed: () { - var locations = API().getLocations(); - List locationList = locations.keys.toList(); - String? selectedLocationID; - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Move Transaction'), - content: Row( - mainAxisSize: MainAxisSize.min, - children: [ - DropdownButton( - value: selectedLocationID, - onChanged: (value) { - selectedLocationID = value!; - API() - .moveTransaction(widget.transaction.uuid, - selectedLocationID!) - .then((x) { - Navigator.of(context).pop(); - }); - - setState(() {}); - }, - items: locationList - .map>((locationID) { - return DropdownMenuItem( - value: locationID, - child: Text(locations[locationID]!.name), - ); - }).toList(), - ), - IconButton( - onPressed: () async { - var locations = API().getLocations(); - var code = await scanQRCode(context, - title: "Scan Location Code"); - if (!locations.containsKey(code)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'The location $code does not exist.')), - ); - return; - } - - API() - .moveTransaction(widget.transaction.uuid, - selectedLocationID!) - .then( - (x) { - Navigator.of(context).pop(); - }, - ); - setState(() {}); - }, - icon: const Icon(Icons.qr_code)) - ], - ), - ); - }, - ); - }, - icon: const Icon(Icons.move_up)) - ], - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column( - children: [ - Row( - children: [ - QRCode( - data: transaction.uuid, - size: 128, - eyeStyle: const QREyeStyle(color: Colors.white), - dataModuleStyle: const QRDataModuleStyle(color: Colors.white), - semanticsLabel: "Transaction UUID", - ), - const SizedBox( - width: 16.0, - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - // todo : human names - Text( - API().getItem(transaction.item).name, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 28), - ), - Text( - API() - .getItem(transaction.item) - .variants[transaction.variant]! - .name, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: Colors.grey), - ), - const SizedBox( - height: 8.0, - ), - Text("Added: ${tsFormat(transaction.timestamp)}"), - ], - ) - ], - ), - const Divider(), - const SizedBox( - height: 12.0, - ), - - if (transaction.expired) const Text("Transaction is Expired!"), - - IconText(Icons.money, transaction.price.toStringAsFixed(2), - color: Colors.green), - - if (transaction.origin != null) - IconText(Icons.store, transaction.origin!, color: Colors.blue), - - if (transaction.location != null) - IconText(Icons.location_city, transaction.location!.name), - - if (transaction.note != null) Text(transaction.note!), - - if (transaction.consumed != null) ...[ - const Divider(), - Text("Consumed on: ${tsFormat(transaction.consumed!.timestamp)}"), - IconText(Icons.store, transaction.consumed!.destination, - color: Colors.blue), - IconText( - Icons.money, transaction.consumed!.price.toStringAsFixed(2), - color: Colors.green), - ] - - // todo : chart with price history - ], - ), - ), - floatingActionButton: transaction.consumed == null - ? FloatingActionButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return ConsumePage(transaction, reload); - }, - )); - }, - child: const Icon(Icons.receipt_long)) - : null, - ); - } -} - -class TransactionCard extends StatelessWidget { - final Transaction t; - final Function refresh; - final Function(Transaction)? onTap; - final Function(Transaction)? onLongPress; - - const TransactionCard(this.t, this.refresh, - {this.onTap, this.onLongPress, super.key}); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () { - if (onTap != null) { - onTap!(t); - return; - } - - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return ConsumePage(t, refresh); - }, - )); - }, - onLongPress: () { - if (onLongPress != null) { - onLongPress!(t); - return; - } - - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return TransactionPage(t); - }, - )); - }, - child: Card( - color: t.expired ? Colors.red[100] : Colors.black, - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - elevation: 4, - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - API().getItem(t.item).name, - style: const TextStyle(fontSize: 16), - ), - const SizedBox( - width: 8, - ), - Text( - API().getItem(t.item).variants[t.variant]!.name, - style: TextStyle(fontSize: 14, color: Colors.grey[400]), - ), - ], - ), - if (t.timestamp != 0) - Text( - tsFormat(t.timestamp), - style: TextStyle(fontSize: 14, color: Colors.grey[700]), - ), - ], - ), - if ((t.note ?? "").isNotEmpty) ...[ - const SizedBox( - height: 4, - ), - Text(t.note!) - ], - const SizedBox( - height: 10, - ), - IconText(Icons.money, "${t.price.toStringAsFixed(2)} €", - color: Colors.green), - if (t.origin != null) ...[ - const SizedBox(height: 8), - IconText(Icons.store, t.origin!, color: Colors.blue) - ], - if (t.location != null) ...[ - const SizedBox( - height: 8, - ), - IconText(Icons.location_city, t.location!.name) - ] - ], - ), - ), - ), - ); - } -} - -String tsFormat(int ts) { - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(ts * 1000); - return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); -} - -class IconText extends StatelessWidget { - final IconData icon; - final String text; - final Color? color; - - const IconText(this.icon, this.text, {super.key, this.color}); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Icon(icon, size: 18, color: color), - const SizedBox(width: 6), - Text( - text, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - overflow: TextOverflow.fade, - ), - ], - ); - } -} - -class TransactionSelectPage extends StatelessWidget { - final Function(Transaction) onSelect; - final List selections; - final List? exclude; - - const TransactionSelectPage(this.selections, - {super.key, required this.onSelect, this.exclude}); - - @override - Widget build(BuildContext context) { - var selectionList = []; - - for (var s in selections) { - if (exclude?.any((x) => x.uuid == s.uuid) ?? false) { - continue; - } - - selectionList.add(s); - } - - return Scaffold( - appBar: AppBar( - title: const Text("Select a Transaction"), - ), - body: ListView( - children: selectionList.isEmpty - ? [ - const ListTile( - title: Center(child: Text("No Transactions available")), - ) - ] - : selectionList - .map((x) => TransactionCard( - x, - () {}, - onLongPress: (x) {}, - onTap: (t) { - onSelect(t); - Navigator.of(context).pop(); - }, - )) - .toList()), - ); - } -} diff --git a/src/setup.rs b/src/setup.rs index 4a12211..0fc4928 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,8 +1,9 @@ use dioxus::prelude::*; -use dioxus_sdk::storage::{new_storage, use_persistent, SessionStorage}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::store::save_to_local_storage; + #[derive(Default, Serialize, Deserialize, Clone, PartialEq)] pub struct Credentials { pub instance_url: String, @@ -45,15 +46,14 @@ pub fn SetupPage() -> Element { evt.prevent_default(); if validate_form() { - let mut creds = - new_storage::("creds".to_string(), || Credentials::default()); + let creds = Credentials::default(); spawn(async move { - let api = crate::api::API::new(creds.read().instance_url.clone()).await; + let api = crate::api::API::new(creds.instance_url.clone()).await; *crate::API.write() = Some(api); }); - creds.set(form_data.read().clone()); + save_to_local_storage("creds", form_data.read().clone()); } }; diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..b14cfab --- /dev/null +++ b/src/store.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; +use web_sys::window; + +pub fn save_to_local_storage_str(key: &str, value: &str) { + let window = window().expect("no global `window` exists"); + let storage = window.local_storage().unwrap().unwrap(); + storage.set_item(key, value).unwrap(); +} + +pub fn load_from_local_storage_str(key: &str) -> Option { + let window = window().expect("no global `window` exists"); + let storage = window.local_storage().unwrap().unwrap(); + storage.get_item(key).unwrap() +} + +pub fn save_to_local_storage(key: &str, value: T) { + let val = serde_json::to_string(&value).unwrap(); + save_to_local_storage_str(key, &val); +} + +pub fn load_from_local_storage Deserialize<'a>>(key: &str) -> Option { + let val = load_from_local_storage_str(key)?; + serde_json::from_str(&val).ok() +}