Compare commits

..

No commits in common. "e366960ce7d220399d28a4062b72b74163bb7d2c" and "4b2c8711dfddeb41a0e722ef5da6720f47ef458f" have entirely different histories.

21 changed files with 431 additions and 603 deletions

View file

@ -41,7 +41,8 @@ android {
} }
defaultConfig { defaultConfig {
applicationId "de.hydrar.red.cdb" // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.cdb_ui"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion

View file

@ -1,7 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="CDB" android:label="cdb_ui"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View file

@ -6,7 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
// todo : api errors // todo : api errors
class API { class API {
SharedPreferences? pref; late SharedPreferences pref;
static final API _instance = API._internal(); static final API _instance = API._internal();
// cache // cache
@ -20,46 +20,18 @@ class API {
API._internal(); API._internal();
Future<void> prefetch() async { Future<void> init() async {
// todo : prefetch
// fetch items
var resp = jsonDecode(await getRequest("$instance/items"));
var lst = resp["items"] as List<dynamic>;
items = lst.map((x) => Item(x)).toList();
// fetch locations
var locResp = jsonDecode(await getRequest("$instance/locations"))
as Map<String, dynamic>;
locations = locResp.map((key, value) => MapEntry(key, Location(value)));
// fetch flowInfos
var flowResp =
jsonDecode(await getRequest("$instance/flows")) as Map<String, dynamic>;
flowInfos = flowResp.map((key, value) => MapEntry(key, FlowInfo(value)));
}
Future<void> init(Function refresh) async {
pref = await SharedPreferences.getInstance(); pref = await SharedPreferences.getInstance();
instance = pref!.getString("instance") ?? ""; instance = pref.getString("instance") ?? "";
refresh();
} }
bool isInit() { bool isInit() {
if (pref == null) { return pref.containsKey("token") && pref.containsKey("instance");
return false;
}
return pref!.containsKey("token") && pref!.containsKey("instance");
}
bool isPrefetched() {
return items != null && locations != null && flowInfos != null;
} }
void save(String instance, String token) { void save(String instance, String token) {
pref!.setString("instance", instance); pref.setString("instance", instance);
pref!.setString("token", token); pref.setString("token", token);
this.instance = instance; this.instance = instance;
} }
@ -69,7 +41,7 @@ class API {
var resp = await http.get(Uri.parse(url), headers: <String, String>{ var resp = await http.get(Uri.parse(url), headers: <String, String>{
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
'Token': pref!.getString("token")! 'Token': pref.getString("token")!
}); });
return utf8.decode(resp.bodyBytes); return utf8.decode(resp.bodyBytes);
@ -80,7 +52,7 @@ class API {
headers: <String, String>{ headers: <String, String>{
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
'Token': pref!.getString("token")! 'Token': pref.getString("token")!
}, },
body: jsonEncode(data)); body: jsonEncode(data));
@ -88,7 +60,14 @@ class API {
} }
// /items // /items
List<Item> getItems() { Future<List<Item>> getItems() async {
if (items != null) {
return items!;
}
var resp = jsonDecode(await getRequest("$instance/items"));
var lst = resp["items"] as List<dynamic>;
items = lst.map((x) => Item(x)).toList();
return items!; return items!;
} }
@ -111,12 +90,23 @@ class API {
return ret; return ret;
} }
Map<String, Location> getLocations() { Future<Map<String, Location>> getLocations() async {
if (locations != null) {
return locations!;
}
var resp = jsonDecode(await getRequest("$instance/locations"))
as Map<String, dynamic>;
locations = resp.map((key, value) => MapEntry(key, Location(value)));
return locations!; return locations!;
} }
Item getItem(String item) { Future<Item> getItem(String item) async {
return items!.firstWhere((x) => x.id == item); if (items != null) {
return items!.firstWhere((x) => x.id == item);
}
return Item(jsonDecode(await getRequest("$instance/item/$item")));
} }
Future<Transaction> getTransaction(String id) async { Future<Transaction> getTransaction(String id) async {
@ -172,7 +162,7 @@ class API {
} }
// /supply // /supply
Future<String> supplyItem(String item, String variant, double price, Future<String> supplyItem(String item, String variant, String price,
String? origin, String? location, String? note) async { String? origin, String? location, String? note) async {
if (origin!.isEmpty) { if (origin!.isEmpty) {
origin = null; origin = null;
@ -201,19 +191,13 @@ class API {
// /demand // /demand
Future<void> consumeItem( Future<void> consumeItem(
String transaction, String destination, double price) async { String transaction, String destination, String price) async {
await postRequest("$instance/demand", await postRequest("$instance/demand",
{"uuid": transaction, "destination": destination, "price": price}); {"uuid": transaction, "destination": destination, "price": price});
} }
// /item/<item_id>/<variant_id>/stat // /item/<item_id>/<variant_id>/stat
Future<dynamic> getStat(String item, String variant, Future<ItemVariantStat> getStat(String item, String variant) async {
{bool full = false}) async {
if (full) {
return FullItemVariantStat(jsonDecode(
await getRequest("$instance/item/$item/$variant/stat?full=true")));
}
return ItemVariantStat( return ItemVariantStat(
jsonDecode(await getRequest("$instance/item/$item/$variant/stat"))); jsonDecode(await getRequest("$instance/item/$item/$variant/stat")));
} }
@ -223,7 +207,7 @@ class API {
} }
// /item/<item_id>/<variant_id>/price_history?<origin> // /item/<item_id>/<variant_id>/price_history?<origin>
Future<List<double>> getPriceHistory(String item, String variant, Future<List<Price>> getPriceHistory(String item, String variant,
{String? origin}) async { {String? origin}) async {
var url = "$instance/item/$item/$variant/price_history"; var url = "$instance/item/$item/$variant/price_history";
@ -233,10 +217,10 @@ class API {
var resp = jsonDecode(await getRequest(url)) as List<dynamic>; var resp = jsonDecode(await getRequest(url)) as List<dynamic>;
return resp.map((x) => x as double).toList(); return resp.map((x) => Price(x)).toList();
} }
Future<double> getLatestPrice(String item, String variant, Future<Price> getLatestPrice(String item, String variant,
{String? origin}) async { {String? origin}) async {
var url = "$instance/item/$item/$variant/price_latest"; var url = "$instance/item/$item/$variant/price_latest";
@ -246,19 +230,31 @@ class API {
var resp = jsonDecode(await getRequest(url)) as Map<String, dynamic>; var resp = jsonDecode(await getRequest(url)) as Map<String, dynamic>;
return resp as double; return Price(resp);
} }
// Flows // Flows
// /flows // /flows
Map<String, FlowInfo> getFlows() { Future<Map<String, FlowInfo>> getFlows() async {
if (flowInfos != null) {
return flowInfos!;
}
var resp =
jsonDecode(await getRequest("$instance/flows")) as Map<String, dynamic>;
flowInfos = resp.map((key, value) => MapEntry(key, FlowInfo(value)));
return flowInfos!; return flowInfos!;
} }
// /flow/<id>/info // /flow/<id>/info
FlowInfo getFlowInfo(String id) { Future<FlowInfo> getFlowInfo(String id) async {
return flowInfos![id]!; if (flowInfos != null) {
return flowInfos![id]!;
}
return FlowInfo(jsonDecode(await getRequest("$instance/flow/$id/info")));
} }
Future<Flow> getFlow(String id) async { Future<Flow> getFlow(String id) async {
@ -281,18 +277,12 @@ class API {
} }
// /flow/<id>/end // /flow/<id>/end
Future<Map<String, List<String>>?> endFlow(String id, Future<List<String>?> endFlow(String id, {List<SupplyForm>? produced}) async {
{List<SupplyForm>? produced}) async {
var resp = jsonDecode(await postRequest("$instance/flow/$id/end", var resp = jsonDecode(await postRequest("$instance/flow/$id/end",
{"produced": produced?.map((x) => x.json()).toList()})); {"produced": produced?.map((x) => x.json()).toList()}));
if (produced != null) { if (produced != null) {
var produced = resp["produced"] as Map<String, dynamic>; return (resp["produced"] as List<dynamic>).cast<String>();
return produced.map(
(key, value) {
return MapEntry(key, (value as List<dynamic>).cast<String>());
},
);
} }
return null; return null;
@ -324,8 +314,13 @@ class API {
"$instance/transaction/$id/move", {"to": newLocation})); "$instance/transaction/$id/move", {"to": newLocation}));
} }
Location getLocation(String id) { Future<Location> getLocation(String id) async {
return locations![id]!; if (locations != null) {
return locations![id]!;
}
var resp = jsonDecode(await getRequest("$instance/location/$id"));
return Location(resp);
} }
Future<String> addNoteToFlow(String flowID, String content) async { Future<String> addNoteToFlow(String flowID, String content) async {
@ -361,14 +356,12 @@ class FlowInfo {
class Item { class Item {
late String id; late String id;
late String? image;
late String name; late String name;
late String? category; late String? category;
late Map<String, ItemVariant> variants; late Map<String, ItemVariant> variants;
Item(Map<String, dynamic> json) { Item(Map<String, dynamic> json) {
id = json["uuid"]; id = json["uuid"];
image = json["image"];
name = json["name"]; name = json["name"];
category = json["category"]; category = json["category"];
variants = <String, ItemVariant>{}; variants = <String, ItemVariant>{};
@ -385,7 +378,6 @@ class ItemVariant {
late String name; late String name;
int? min; int? min;
int? expiry; int? expiry;
List<int>? barcodes;
ItemVariant(Map<String, dynamic> json) { ItemVariant(Map<String, dynamic> json) {
item = json["item"]; item = json["item"];
@ -393,9 +385,26 @@ class ItemVariant {
name = json["name"]; name = json["name"];
min = json["min"]; min = json["min"];
expiry = json["expiry"]; expiry = json["expiry"];
barcodes = json["barcodes"] != null }
? (json["barcodes"] as List<dynamic>).cast<int>() }
: null;
class Price {
late double value;
late String currency;
Price(Map<String, dynamic> json) {
value = json["value"];
currency = json["currency"];
}
Price.fromString(String value) {
var priceSplit = value.split(" ");
this.value = double.parse(priceSplit[0]);
currency = priceSplit[1];
}
String format() {
return "${value.toStringAsFixed(2)} $currency";
} }
} }
@ -403,7 +412,7 @@ class Transaction {
late String uuid; late String uuid;
late String item; late String item;
late String variant; late String variant;
late double price; late Price price;
String? origin; String? origin;
late int timestamp; late int timestamp;
ConsumeInfo? consumed; ConsumeInfo? consumed;
@ -415,7 +424,7 @@ class Transaction {
uuid = json["uuid"]; uuid = json["uuid"];
item = json["item"]; item = json["item"];
variant = json["variant"]; variant = json["variant"];
price = json["price"]; price = Price(json["price"]);
origin = json["origin"]; origin = json["origin"];
timestamp = json["timestamp"]; timestamp = json["timestamp"];
expired = json["expired"]; expired = json["expired"];
@ -424,11 +433,12 @@ class Transaction {
location = json["location"] != null ? Location(json["location"]) : null; location = json["location"] != null ? Location(json["location"]) : null;
} }
Transaction.inMemory(String itemID, String variantID, this.price, Transaction.inMemory(String itemID, String variantID, String price,
String? origin, Location? location, String? note) { String? origin, Location? location, String? note) {
uuid = ""; uuid = "";
item = itemID; item = itemID;
variant = variantID; variant = variantID;
this.price = Price.fromString(price);
origin = origin; origin = origin;
timestamp = 0; timestamp = 0;
consumed = null; consumed = null;
@ -440,12 +450,12 @@ class Transaction {
class ConsumeInfo { class ConsumeInfo {
late String destination; late String destination;
late double price; late Price price;
late int timestamp; late int timestamp;
ConsumeInfo(Map<String, dynamic> json) { ConsumeInfo(Map<String, dynamic> json) {
destination = json["destination"]; destination = json["destination"];
price = json["price"]; price = Price(json["price"]);
timestamp = json["timestamp"]; timestamp = json["timestamp"];
} }
} }
@ -553,14 +563,14 @@ class FlowNote {
} }
class GlobalItemStat { class GlobalItemStat {
late int itemCount; late int item_count;
late int totalTransactions; late int total_transactions;
late double totalPrice; late double total_price;
GlobalItemStat(Map<String, dynamic> json) { GlobalItemStat(Map<String, dynamic> json) {
itemCount = json["item_count"]; item_count = json["item_count"];
totalTransactions = json["total_transactions"]; total_transactions = json["total_transactions"];
totalPrice = json["total_price"]; total_price = json["total_price"];
} }
} }
@ -568,28 +578,3 @@ class GlobalItemStat {
var split = iv.split("::"); var split = iv.split("::");
return (split[0], split[1]); return (split[0], split[1]);
} }
class FullItemVariantStat {
late int amount;
late double totalPrice;
late double expiryRate;
late Map<String, OriginStat> origins;
FullItemVariantStat(Map<String, dynamic> json) {
amount = json["amount"];
totalPrice = json["total_price"];
expiryRate = json["expiry_rate"];
origins = (json["origins"] as Map<String, dynamic>)
.map((key, value) => MapEntry(key, OriginStat(value)));
}
}
class OriginStat {
late double averagePrice;
late int inventory;
OriginStat(Map<String, dynamic> json) {
averagePrice = json["average_price"];
inventory = json["inventory"];
}
}

View file

@ -7,53 +7,24 @@ import 'package:cdb_ui/pages/stats.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Future<void> main() async { Future<void> main() async {
await API().init();
runApp(const MyApp()); runApp(const MyApp());
} }
class MyApp extends StatefulWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'CDB', title: 'CDB',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple, brightness: Brightness.dark), seedColor: Colors.deepPurple, brightness: Brightness.dark),
useMaterial3: true, useMaterial3: true,
), ),
home: API().isInit() home: API().isInit() ? const MyHomePage() : const SetupPage(),
? (API().isPrefetched() );
? const MyHomePage()
: const Scaffold(
body: Center(child: CircularProgressIndicator()),
))
: const SetupPage());
} }
} }

View file

@ -27,8 +27,8 @@ class _ConsumePageState extends State<ConsumePage> {
_formKey.currentState!.save(); _formKey.currentState!.save();
API() API()
.consumeItem(widget.transaction.uuid, _selectedDestination, .consumeItem(
double.parse(_price)) widget.transaction.uuid, _selectedDestination, "$_price")
.then((_) { .then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Item consumed successfully!')), const SnackBar(content: Text('Item consumed successfully!')),

View file

@ -56,7 +56,7 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
), ),
); );
if (confirm ?? false) { if (confirm) {
await API await API
.API() .API()
.endFlow(widget.flow.id) .endFlow(widget.flow.id)
@ -67,22 +67,23 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
if (widget.info.next != null) if (widget.info.next != null)
IconButton( IconButton(
onPressed: () { onPressed: () {
var newInfo = API.API().getFlowInfo(widget.info.next!); API.API().getFlowInfo(widget.info.next!).then((newInfo) {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) { builder: (context) {
return CreateFlowPage( return CreateFlowPage(
newInfo, newInfo,
() {}, () {},
previousFlow: widget.flow, previousFlow: widget.flow,
); );
}, },
)); ));
});
}, },
icon: const Icon(Icons.arrow_forward)), icon: const Icon(Icons.arrow_forward)),
], ],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0), padding: EdgeInsets.symmetric(horizontal: 18.0),
child: Column( child: Column(
children: [ children: [
Row( Row(

View file

@ -25,10 +25,11 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
input: depends.map((x) => x.uuid).toList()) input: depends.map((x) => x.uuid).toList())
.then((x) { .then((x) {
API.API().getFlow(x).then((flow) { API.API().getFlow(x).then((flow) {
var info = API.API().getFlowInfo(flow.kind); API.API().getFlowInfo(flow.kind).then((info) {
Navigator.of(context).pushReplacement(MaterialPageRoute( Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ActiveFlowPage(flow, info), builder: (context) => ActiveFlowPage(flow, info),
)); ));
});
}); });
}); });
return; return;
@ -64,42 +65,26 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
}); });
} }
List<Widget> buildInputSelection(BuildContext context) { Widget buildInputSelection(BuildContext context) {
if (widget.info.depends.isEmpty) { return Column(
return []; children: widget.info.depends.map((x) {
} return ElevatedButton(
onPressed: () {
return [ selectDependItems(context, x);
Column( },
children: widget.info.depends.map((x) { child: Text("Add $x"));
var (item, variant) = API.itemVariant(x); }).toList());
return ElevatedButton(
onPressed: () {
selectDependItems(context, x);
},
child:
Text("Add ${API.API().getItem(item).variants[variant]!.name}"));
}).toList()),
const Divider(),
];
} }
Widget flowContinuation() { Widget flowContinuation() {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Card( Card(
child: Padding( child: Text(widget.previousFlow!.kind),
padding: const EdgeInsets.all(8.0),
child: Text(API.API().getFlowInfo(widget.previousFlow!.kind).name),
),
), ),
const Card(child: Icon(Icons.arrow_right)), const Text(" -> "),
Card( Card(
child: Padding( child: Text(widget.info.name),
padding: const EdgeInsets.all(8.0),
child: Text(widget.info.name),
),
) )
], ],
); );
@ -108,17 +93,15 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: Text("Create new ${widget.info.name} Flow")),
title: Text(widget.previousFlow != null
? "Continue to ${widget.previousFlow!.kind}"
: "Create new ${widget.info.name} Flow")),
body: Column( body: Column(
children: [ children: [
if (widget.previousFlow != null) ...[ if (widget.previousFlow != null) ...[
flowContinuation(), flowContinuation(),
const Divider() const Divider()
], ],
...buildInputSelection(context), buildInputSelection(context),
const Divider(),
Card( Card(
child: Column( child: Column(
children: depends children: depends

View file

@ -1,3 +1,5 @@
import 'dart:js_interop_unsafe';
import 'package:cdb_ui/api.dart' as API; import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/supply.dart'; import 'package:cdb_ui/pages/supply.dart';
@ -21,7 +23,13 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
locations = API.API().getLocations(); API.API().getLocations().then(
(value) {
setState(() {
locations = value;
});
},
);
} }
refresh() { refresh() {
@ -38,24 +46,25 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
List<Widget> ret = []; List<Widget> ret = [];
for (var i in widget.info.produces!) { for (var i in widget.info.produces!) {
var (itemID, variant) = API.itemVariant(i);
var item = API.API().getItem(itemID);
ret.add(ElevatedButton( ret.add(ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute( var (itemID, variant) = API.itemVariant(i);
builder: (context) { API.API().getItem(itemID).then((item) {
return SupplyPage( Navigator.of(context).push(MaterialPageRoute(
item, builder: (context) {
refresh, return SupplyPage(
onlyVariants: [variant], item,
forcePrice: "0.00", refresh,
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}", onlyVariants: [variant],
onCreate: addProduced, forcePrice: "0.00",
); forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
}, onCreate: addProduced,
)); );
},
));
});
}, },
child: Text("Produced ${item.variants[variant]!.name}"))); child: Text("Produced $i")));
} }
return ret; return ret;

View file

@ -24,17 +24,13 @@ class _FlowInfoPageState extends State<FlowInfoPage> {
body: Column( body: Column(
children: [ children: [
// todo : ui improve // todo : ui improve
if (widget.info.next != null) if (widget.info.next != null) Text("Next: ${widget.info.next}"),
Text("Next: ${API.API().getFlowInfo(widget.info.next!).name}"),
if (widget.info.depends.isNotEmpty) if (widget.info.depends.isNotEmpty)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
const Text("Flow can use: "), const Text("Flow can use: "),
...widget.info.depends.map((x) { ...widget.info.depends.map((x) => Text(x)).toList(),
var (item, variant) = API.itemVariant(x);
return Text(API.API().getItem(item).variants[variant]!.name);
}).toList(),
], ],
), ),
if (widget.info.produces?.isNotEmpty ?? false) if (widget.info.produces?.isNotEmpty ?? false)
@ -42,10 +38,7 @@ class _FlowInfoPageState extends State<FlowInfoPage> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
const Text("Flow can produce: "), const Text("Flow can produce: "),
...widget.info.produces!.map((x) { ...widget.info.produces!.map((x) => Text(x)).toList(),
var (item, variant) = API.itemVariant(x);
return Text(API.API().getItem(item).variants[variant]!.name);
}).toList(),
], ],
), ),
const Divider(), const Divider(),

View file

@ -2,8 +2,9 @@ import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/expandable_list.dart'; import 'package:cdb_ui/pages/expandable_list.dart';
import 'package:cdb_ui/pages/flow/active_flow_page.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/flow/flow_info_page.dart';
import 'package:cdb_ui/pages/supply.dart'; import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
class FlowsPage extends StatefulWidget { class FlowsPage extends StatefulWidget {
const FlowsPage({super.key}); const FlowsPage({super.key});
@ -19,7 +20,13 @@ class _FlowsPageState extends State<FlowsPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
flowInfos = API.API().getFlows(); API.API().getFlows().then(
(value) {
setState(() {
flowInfos = value;
});
},
);
} }
Widget flowTile(BuildContext context, API.FlowInfo x) { Widget flowTile(BuildContext context, API.FlowInfo x) {
@ -87,8 +94,7 @@ class _FlowsPageState extends State<FlowsPage> {
children: producedMapping[key]!.map((x) { children: producedMapping[key]!.map((x) {
return flowTile(context, x); return flowTile(context, x);
}).toList()); }).toList());
items.add(ExpandableListItem( items.add(ExpandableListItem(body: flows, header: Text(key)));
body: flows, header: Text(API.API().getItem(key).name)));
} }
return ExpandableList(items); return ExpandableList(items);
@ -116,8 +122,7 @@ class _FlowsPageState extends State<FlowsPage> {
children: dependsMapping[key]!.map((x) { children: dependsMapping[key]!.map((x) {
return flowTile(context, x); return flowTile(context, x);
}).toList()); }).toList());
items.add(ExpandableListItem( items.add(ExpandableListItem(body: flows, header: Text(key)));
body: flows, header: Text(API.API().getItem(key).name)));
} }
return ExpandableList(items); return ExpandableList(items);
@ -155,18 +160,25 @@ class _FlowsPageState extends State<FlowsPage> {
_ => const Text("..."), _ => const Text("..."),
}, },
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () {
// scan flow code // scan flow code
var code = await scanQRCode(context, title: "Scan Flow Code"); QrBarCodeScannerDialog().getScannedQrBarCode(
context: context,
onCode: (code) {
// library is retarded
code = code!.replaceFirst("Code scanned = ", "");
API.API().getFlow(code!).then((flow) { API.API().getFlow(code).then((flow) {
var info = API.API().getFlowInfo(flow.kind); API.API().getFlowInfo(flow.kind).then((info) {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) { builder: (context) {
return ActiveFlowPage(flow, info); return ActiveFlowPage(flow, info);
}, },
)); ));
}); });
});
},
);
}, },
child: const Icon(Icons.qr_code), child: const Icon(Icons.qr_code),
), ),

View file

@ -1,61 +1,48 @@
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/itemview.dart'; import 'package:cdb_ui/pages/itemview.dart';
import 'package:cdb_ui/pages/supply.dart';
import 'package:cdb_ui/pages/transaction.dart'; import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
class ItemsPage extends StatelessWidget { class ItemsPage extends StatelessWidget {
const ItemsPage({super.key}); const ItemsPage({super.key});
_scanBarcodeSupply(BuildContext context) async {
var code =
int.parse(await scanQRCode(context, title: "Scan Barcode") ?? "0");
var items = API().getItems();
for (var item in items) {
for (var variant in item.variants.keys) {
for (var barcode in item.variants[variant]!.barcodes ?? []) {
if (code == barcode) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SupplyPage(
item,
() {},
onlyVariants: [variant],
)));
}
}
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Items"), title: const Text("Items"),
// todo : add barcode scan
actions: [
ElevatedButton(
onPressed: () {
_scanBarcodeSupply(context);
},
child: const Icon(Icons.add_box))
],
), ),
body: ListView( body: FutureBuilder(
children: API().getItems().map((x) { future: API().getItems(),
return ItemTile(x); builder: (context, snapshot) {
}).toList()), if (!snapshot.hasData) {
floatingActionButton: FloatingActionButton( return const CircularProgressIndicator();
onPressed: () async { }
// scan transaction code
var code = await scanQRCode(context, title: "Scan Transaction Code");
API().getTransaction(code!).then((t) { var items = snapshot.data!;
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TransactionPage(t), return ListView(
)); children: items.map((x) {
}); return ItemTile(x);
}).toList());
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
// scan transaction code
QrBarCodeScannerDialog().getScannedQrBarCode(
context: context,
onCode: (code) {
// library is retarded
code = code!.replaceFirst("Code scanned = ", "");
API().getTransaction(code).then((t) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TransactionPage(t),
));
});
},
);
}, },
child: const Icon(Icons.qr_code), child: const Icon(Icons.qr_code),
), ),

View file

@ -2,6 +2,7 @@ import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/stats.dart'; import 'package:cdb_ui/pages/stats.dart';
import 'package:cdb_ui/pages/transaction.dart'; import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'supply.dart'; import 'supply.dart';
// todo : show est. time remaining until inventory gets empty (based on demand) // todo : show est. time remaining until inventory gets empty (based on demand)
@ -32,7 +33,7 @@ class _ItemViewState extends State<ItemView> {
builder: (context) => ItemStatPage(widget.item), builder: (context) => ItemStatPage(widget.item),
)); ));
}, },
icon: const Icon(Icons.bar_chart)) icon: Icon(Icons.bar_chart))
], ],
), ),
body: Column(children: [ body: Column(children: [
@ -43,15 +44,12 @@ class _ItemViewState extends State<ItemView> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Align( const Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: widget.item.image != null child: Placeholder(
? Image.network( fallbackWidth: 100,
"${API().instance}/${widget.item.image}", fallbackHeight: 100,
height: 100, ),
width: 100,
)
: null,
), ),
const SizedBox( const SizedBox(
width: 16.0, width: 16.0,

View file

@ -1,8 +1,8 @@
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/supply.dart';
import 'package:cdb_ui/pages/transaction.dart'; import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart'; import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
class LocationsPage extends StatefulWidget { class LocationsPage extends StatefulWidget {
const LocationsPage({super.key}); const LocationsPage({super.key});
@ -17,7 +17,13 @@ class _LocationsPageState extends State<LocationsPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
locations = API().getLocations(); API().getLocations().then(
(value) {
setState(() {
locations = value;
});
},
);
} }
TreeNode buildTree(BuildContext context, String locID) { TreeNode buildTree(BuildContext context, String locID) {
@ -63,20 +69,25 @@ class _LocationsPageState extends State<LocationsPage> {
return buildTree(context, key); return buildTree(context, key);
}).toList()), }).toList()),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () {
// scan location code // scan location code
var code = await scanQRCode(context, title: "Scan Location Code"); QrBarCodeScannerDialog().getScannedQrBarCode(
context: context,
onCode: (code) {
// library is retarded
code = code!.replaceFirst("Code scanned = ", "");
if (!locations!.containsKey(code)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('The location $code does not exist.')),
);
return;
}
if (!locations!.containsKey(code)) { Navigator.of(context).push(MaterialPageRoute(
ScaffoldMessenger.of(context).showSnackBar( builder: (context) => LocationView(locations![code]!),
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), child: const Icon(Icons.qr_code),
), ),
@ -107,7 +118,7 @@ class _LocationViewState extends State<LocationView> {
title: Text(widget.location.name), title: Text(widget.location.name),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0), padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Column( child: Column(
children: [ children: [
Card( Card(

View file

@ -31,8 +31,6 @@ class _SetupPageState extends State<SetupPage> {
const SnackBar(content: Text('Setup Complete!')), const SnackBar(content: Text('Setup Complete!')),
); );
await API().prefetch();
// Navigate or close the setup screen // Navigate or close the setup screen
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,

View file

@ -1,5 +1,4 @@
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/expandable_list.dart';
import 'package:cdb_ui/pages/transaction.dart'; import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -24,9 +23,6 @@ class StatsPage extends StatelessWidget {
// global origin / destinations // global origin / destinations
return Scaffold( return Scaffold(
appBar: AppBar(
title: const Text("Home"),
),
body: FutureBuilder( body: FutureBuilder(
future: _fetchData(), future: _fetchData(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -49,36 +45,26 @@ class StatsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
IconText(Icons.data_object, IconText(Icons.data_object,
"Items: ${globalStat.itemCount}"), "Items: ${globalStat.item_count}"),
IconText(Icons.list_alt, IconText(Icons.list_alt,
"Inventory: ${globalStat.totalTransactions}"), "Inventory: ${globalStat.total_transactions}"),
IconText(Icons.money, IconText(Icons.money,
"Price: ${globalStat.totalPrice.toStringAsFixed(2)}") "Price: ${globalStat.total_price.toStringAsFixed(2)}")
], ],
), ),
)), )),
if (min.isNotEmpty) if (min.isNotEmpty)
const ListTile( const ListTile(title: Text("Items under Minimum")),
title: Text(
"Items under Minimum",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
)),
...min.map((item) { ...min.map((item) {
var (itemID, variant) = itemVariant(item.itemVariant);
var name = API().getItem(itemID).variants[variant]!.name;
return ListTile( return ListTile(
title: Text("$name under minimum. Needs ${item.need} more."), title: Text(
"Item ${item.itemVariant} under minimum. Needs ${item.need} more."),
); );
}).toList(), }).toList(),
if (expired.isNotEmpty) if (expired.isNotEmpty)
const ListTile( const ListTile(title: Text("Expired Items")),
title: Text(
"Expired Items",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
)),
// Mapping expired list to widgets // Mapping expired list to widgets
...expired.map((item) { ...expired.map((item) {
@ -99,48 +85,16 @@ class ItemStatPage extends StatelessWidget {
const ItemStatPage(this.item, {super.key}); const ItemStatPage(this.item, {super.key});
// todo : expiry ratio
// todo : avg time of transaction active // todo : avg time of transaction active
ExpandableListItem buildVariantStat(String variant) {
return ExpandableListItem(
body: FutureBuilder(
future: API().getStat(item.id, variant, full: true),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
var data = snapshot.data! as FullItemVariantStat;
return Column(
children: [
Text("Amount: ${data.amount}"),
Text("Total Cost: ${data.totalPrice}"),
Text("Expiry Rate: ${data.expiryRate}"),
...data.origins.keys.map((key) {
var originStat = data.origins[key]!;
return Column(children: [
Text("Inventory: ${originStat.inventory}"),
Text(
"Average Price: ${originStat.averagePrice.toStringAsFixed(2)}"),
]);
}).toList()
],
);
},
),
header: Text(item.variants[variant]!.name));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Statistics of ${item.name}"), title: Text("Statistics of ${item.name}"),
), ),
body: Column(children: [ body: null,
ExpandableList(item.variants.keys.map(buildVariantStat).toList())
]),
); );
} }
} }

View file

@ -1,21 +1,6 @@
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:simple_barcode_scanner/enum.dart'; import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
Future<String?> 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 { class SupplyPage extends StatefulWidget {
final Item item; final Item item;
@ -68,7 +53,7 @@ class _SupplyPageState extends State<SupplyPage> {
var t = SupplyForm( var t = SupplyForm(
itemID: widget.item.id, itemID: widget.item.id,
variant: variant, variant: variant,
price: double.parse(_priceController.text), price: "${_priceController.text}",
origin: _selectedOrigin, origin: _selectedOrigin,
location: _selectedLocation, location: _selectedLocation,
note: _noteController.text); note: _noteController.text);
@ -81,13 +66,8 @@ class _SupplyPageState extends State<SupplyPage> {
} }
API() API()
.supplyItem( .supplyItem(widget.item.id, variant, "${_priceController.text}",
widget.item.id, _selectedOrigin, _selectedLocation, _noteController.text)
variant,
double.parse(_priceController.text),
_selectedOrigin,
_selectedLocation,
_noteController.text)
.then((_) { .then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Item added successfully!')), const SnackBar(content: Text('Item added successfully!')),
@ -146,7 +126,7 @@ class _SupplyPageState extends State<SupplyPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Origin Field with Dropdown and Text Input // Origin Field with Dropdown and Text Input
if (widget.forceOrigin == null) ...[ if (widget.forceOrigin == null)
AutocompletedTextField( AutocompletedTextField(
options: origins, options: origins,
getValue: () => _selectedOrigin, getValue: () => _selectedOrigin,
@ -158,7 +138,7 @@ class _SupplyPageState extends State<SupplyPage> {
? await API() ? await API()
.getLatestPrice(widget.item.id, variant, .getLatestPrice(widget.item.id, variant,
origin: selection) origin: selection)
.then((x) => x.toStringAsFixed(2)) .then((x) => x.value.toStringAsFixed(2))
: _priceController.text; : _priceController.text;
setState(() { setState(() {
_priceController.text = price; _priceController.text = price;
@ -166,11 +146,11 @@ class _SupplyPageState extends State<SupplyPage> {
}); });
}, },
label: "Origin"), label: "Origin"),
const SizedBox(height: 16),
], const SizedBox(height: 16),
// Price Field // Price Field
if (widget.forcePrice == null) ...[ if (widget.forcePrice == null)
TextFormField( TextFormField(
decoration: const InputDecoration(labelText: 'Price'), decoration: const InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
@ -185,8 +165,8 @@ class _SupplyPageState extends State<SupplyPage> {
return null; return null;
}, },
), ),
const SizedBox(height: 16),
], const SizedBox(height: 16),
// Location Dropdown // Location Dropdown
Row( Row(
@ -217,13 +197,15 @@ class _SupplyPageState extends State<SupplyPage> {
width: 12, width: 12,
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () {
var code = await scanQRCode(context); QrBarCodeScannerDialog().getScannedQrBarCode(
setState(() { context: context,
if (API().getLocations().keys.contains(code)) { onCode: (code) {
_selectedLocation = code!; setState(() {
} _selectedLocation = code!;
}); });
},
);
}, },
icon: const Icon(Icons.qr_code), icon: const Icon(Icons.qr_code),
), ),
@ -253,7 +235,7 @@ class _SupplyPageState extends State<SupplyPage> {
} }
Future<Map<String, dynamic>> _fetchData() async { Future<Map<String, dynamic>> _fetchData() async {
var locations = API().getLocations(); var locations = await API().getLocations();
var origins = await API().getUniqueField(widget.item.id, variant, "origin"); var origins = await API().getUniqueField(widget.item.id, variant, "origin");
origins.insert(0, ""); origins.insert(0, "");
locations[""] = Location.zero(); locations[""] = Location.zero();
@ -311,7 +293,7 @@ class AutocompletedTextField extends StatelessWidget {
class SupplyForm { class SupplyForm {
final String itemID; final String itemID;
final String variant; final String variant;
final double price; final String price;
final String? origin; final String? origin;
final String? location; final String? location;
final String note; final String note;

View file

@ -1,9 +1,9 @@
import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/consume.dart'; import 'package:cdb_ui/pages/consume.dart';
import 'package:cdb_ui/pages/supply.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:qr_bar_code/qr/qr.dart'; import 'package:qr_bar_code/qr/qr.dart';
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
class TransactionPage extends StatefulWidget { class TransactionPage extends StatefulWidget {
final Transaction transaction; final Transaction transaction;
@ -40,73 +40,84 @@ class _TransactionPageState extends State<TransactionPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(API().getItem(transaction.item).name), title: Text(widget.transaction.item),
actions: [ actions: [
IconButton( IconButton(
onPressed: () { onPressed: () {
var locations = API().getLocations(); API().getLocations().then((locations) {
List<String> locationList = locations.keys.toList(); List<String> locationList = locations.keys.toList();
String? selectedLocationID; String? selectedLocationID;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Move Transaction'),
content: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton<String>(
value: selectedLocationID,
onChanged: (value) {
selectedLocationID = value!;
API()
.moveTransaction(widget.transaction.uuid,
selectedLocationID!)
.then((x) {
Navigator.of(context).pop();
});
setState(() {});
},
items: locationList
.map<DropdownMenuItem<String>>((locationID) {
return DropdownMenuItem<String>(
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;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Move Transaction'),
content: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton<String>(
value: selectedLocationID,
onChanged: (value) {
selectedLocationID = value!;
API() API()
.moveTransaction(widget.transaction.uuid, .moveTransaction(widget.transaction.uuid,
selectedLocationID!) selectedLocationID!)
.then( .then((x) {
(x) { Navigator.of(context).pop();
Navigator.of(context).pop(); });
},
);
setState(() {}); setState(() {});
}, },
icon: const Icon(Icons.qr_code)) items: locationList
], .map<DropdownMenuItem<String>>((locationID) {
), return DropdownMenuItem<String>(
); value: locationID,
}, child: Text(locations[locationID]!.name),
); );
}).toList(),
),
IconButton(
onPressed: () {
API().getLocations().then((locations) {
QrBarCodeScannerDialog()
.getScannedQrBarCode(
context: context,
onCode: (code) {
// library is retarded
code = code!.replaceFirst(
"Code scanned = ", "");
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)) icon: const Icon(Icons.move_up))
], ],
@ -132,15 +143,12 @@ class _TransactionPageState extends State<TransactionPage> {
children: [ children: [
// todo : human names // todo : human names
Text( Text(
API().getItem(transaction.item).name, "${transaction.item}",
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 28), fontWeight: FontWeight.bold, fontSize: 28),
), ),
Text( Text(
API() "${transaction.variant}",
.getItem(transaction.item)
.variants[transaction.variant]!
.name,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 20, fontSize: 20,
@ -161,7 +169,7 @@ class _TransactionPageState extends State<TransactionPage> {
if (transaction.expired) const Text("Transaction is Expired!"), if (transaction.expired) const Text("Transaction is Expired!"),
IconText(Icons.money, transaction.price.toStringAsFixed(2), IconText(Icons.money, transaction.price.format(),
color: Colors.green), color: Colors.green),
if (transaction.origin != null) if (transaction.origin != null)
@ -177,8 +185,7 @@ class _TransactionPageState extends State<TransactionPage> {
Text("Consumed on: ${tsFormat(transaction.consumed!.timestamp)}"), Text("Consumed on: ${tsFormat(transaction.consumed!.timestamp)}"),
IconText(Icons.store, transaction.consumed!.destination, IconText(Icons.store, transaction.consumed!.destination,
color: Colors.blue), color: Colors.blue),
IconText( IconText(Icons.money, transaction.consumed!.price.format(),
Icons.money, transaction.consumed!.price.toStringAsFixed(2),
color: Colors.green), color: Colors.green),
] ]
@ -255,14 +262,14 @@ class TransactionCard extends StatelessWidget {
Row( Row(
children: [ children: [
Text( Text(
API().getItem(t.item).name, t.item,
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
), ),
const SizedBox( const SizedBox(
width: 8, width: 4,
), ),
Text( Text(
API().getItem(t.item).variants[t.variant]!.name, t.variant,
style: TextStyle(fontSize: 14, color: Colors.grey[400]), style: TextStyle(fontSize: 14, color: Colors.grey[400]),
), ),
], ],
@ -283,7 +290,8 @@ class TransactionCard extends StatelessWidget {
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
IconText(Icons.money, "${t.price.toStringAsFixed(2)}", IconText(Icons.money,
"${t.price.value.toStringAsFixed(2)} ${t.price.currency}",
color: Colors.green), color: Colors.green),
if (t.origin != null) ...[ if (t.origin != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@ -324,7 +332,6 @@ class IconText extends StatelessWidget {
Text( Text(
text, text,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
overflow: TextOverflow.fade,
), ),
], ],
); );
@ -351,28 +358,29 @@ class TransactionSelectPage extends StatelessWidget {
selectionList.add(s); selectionList.add(s);
} }
if (selectionList.isEmpty) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No Transactions to select')),
);
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Select a Transaction"), title: const Text("Select a Transaction"),
), ),
body: ListView( body: ListView(
children: selectionList.isEmpty children: selectionList
? [ .map((x) => TransactionCard(
const ListTile( x,
title: Center(child: Text("No Transactions available")), () {},
) onLongPress: (x) {},
] onTap: (t) {
: selectionList onSelect(t);
.map((x) => TransactionCard( Navigator.of(context).pop();
x, },
() {}, ))
onLongPress: (x) {}, .toList()),
onTap: (t) {
onSelect(t);
Navigator.of(context).pop();
},
))
.toList()),
); );
} }
} }

View file

@ -94,14 +94,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_barcode_scanner:
dependency: transitive
description:
name: flutter_barcode_scanner
sha256: a4ba37daf9933f451a5e812c753ddd045d6354e4a3280342d895b07fecaab3fa
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -110,14 +102,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev"
source: hosted
version: "2.0.22"
flutter_simple_treeview: flutter_simple_treeview:
dependency: "direct main" dependency: "direct main"
description: description:
@ -140,10 +124,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.2" version: "1.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -160,22 +144,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.18.1"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.5" version: "10.0.4"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.5" version: "3.0.3"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
@ -204,18 +196,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.0" version: "1.12.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -248,54 +240,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
permission_handler:
dependency: transitive
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
url: "https://pub.dev"
source: hosted
version: "12.0.12"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
url: "https://pub.dev"
source: hosted
version: "4.2.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -320,6 +264,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
qr_bar_code_scanner_dialog:
dependency: "direct main"
description:
name: qr_bar_code_scanner_dialog
sha256: dcb937816d4e562141530265bd1ca39fe00f57000fd79e26c163c957d443e9e4
url: "https://pub.dev"
source: hosted
version: "0.0.5"
qr_code_scanner:
dependency: transitive
description:
name: qr_code_scanner
sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd
url: "https://pub.dev"
source: hosted
version: "1.0.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -376,14 +336,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.1"
simple_barcode_scanner:
dependency: "direct main"
description:
name: simple_barcode_scanner
sha256: "52b30082ebd6fab1e6314cb9bfc1aca5372890616dcb89d0e254edf7b7ef4951"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -433,10 +385,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2" version: "0.7.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -457,26 +409,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.5" version: "14.2.1"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "0.5.1"
webview_windows:
dependency: transitive
description:
name: webview_windows
sha256: "47fcad5875a45db29dbb5c9e6709bf5c88dcc429049872701343f91ed7255730"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -486,5 +430,5 @@ packages:
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
sdks: sdks:
dart: ">=3.5.3 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.22.0"

View file

@ -1,5 +1,5 @@
name: cdb_ui name: cdb_ui
description: Economic Database. description: A new Flutter project.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@ -32,7 +32,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_simple_treeview: ^3.0.2 flutter_simple_treeview: ^3.0.2
qr_bar_code: ^1.3.0 qr_bar_code: ^1.3.0
simple_barcode_scanner: ^0.1.2 qr_bar_code_scanner_dialog: ^0.0.5
intl: ^0.18.0 intl: ^0.18.0
shared_preferences: ^2.1.0 shared_preferences: ^2.1.0
fl_chart: ^0.69.0 fl_chart: ^0.69.0

View file

@ -6,15 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <qr_bar_code/qr_bar_code_plugin_c_api.h> #include <qr_bar_code/qr_bar_code_plugin_c_api.h>
#include <webview_windows/webview_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
QrBarCodePluginCApiRegisterWithRegistrar( QrBarCodePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("QrBarCodePluginCApi")); registry->GetRegistrarForPlugin("QrBarCodePluginCApi"));
WebviewWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WebviewWindowsPlugin"));
} }

View file

@ -3,9 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
qr_bar_code qr_bar_code
webview_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST