Compare commits
10 commits
4b2c8711df
...
e366960ce7
Author | SHA1 | Date | |
---|---|---|---|
e366960ce7 | |||
7ee1cda550 | |||
61134f7b63 | |||
0e2ccb9e54 | |||
fcecb5aad5 | |||
b13b385cb7 | |||
d21554cddc | |||
167a4620fe | |||
2f24e02696 | |||
a608c50447 |
21 changed files with 601 additions and 429 deletions
|
@ -41,8 +41,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
applicationId "de.hydrar.red.cdb"
|
||||||
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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<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_ui"
|
android:label="CDB"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|
201
lib/api.dart
201
lib/api.dart
|
@ -6,7 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
// todo : api errors
|
// todo : api errors
|
||||||
|
|
||||||
class API {
|
class API {
|
||||||
late SharedPreferences pref;
|
SharedPreferences? pref;
|
||||||
static final API _instance = API._internal();
|
static final API _instance = API._internal();
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
@ -20,18 +20,46 @@ class API {
|
||||||
|
|
||||||
API._internal();
|
API._internal();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> prefetch() 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() {
|
||||||
return pref.containsKey("token") && pref.containsKey("instance");
|
if (pref == null) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +69,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);
|
||||||
|
@ -52,7 +80,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));
|
||||||
|
|
||||||
|
@ -60,14 +88,7 @@ class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /items
|
// /items
|
||||||
Future<List<Item>> getItems() async {
|
List<Item> getItems() {
|
||||||
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!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,23 +111,12 @@ class API {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, Location>> getLocations() async {
|
Map<String, Location> getLocations() {
|
||||||
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!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Item> getItem(String item) async {
|
Item getItem(String item) {
|
||||||
if (items != null) {
|
return items!.firstWhere((x) => x.id == item);
|
||||||
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 {
|
||||||
|
@ -162,7 +172,7 @@ class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /supply
|
// /supply
|
||||||
Future<String> supplyItem(String item, String variant, String price,
|
Future<String> supplyItem(String item, String variant, double 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;
|
||||||
|
@ -191,13 +201,19 @@ class API {
|
||||||
|
|
||||||
// /demand
|
// /demand
|
||||||
Future<void> consumeItem(
|
Future<void> consumeItem(
|
||||||
String transaction, String destination, String price) async {
|
String transaction, String destination, double 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<ItemVariantStat> getStat(String item, String variant) async {
|
Future<dynamic> getStat(String item, String variant,
|
||||||
|
{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")));
|
||||||
}
|
}
|
||||||
|
@ -207,7 +223,7 @@ class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /item/<item_id>/<variant_id>/price_history?<origin>
|
// /item/<item_id>/<variant_id>/price_history?<origin>
|
||||||
Future<List<Price>> getPriceHistory(String item, String variant,
|
Future<List<double>> 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";
|
||||||
|
|
||||||
|
@ -217,10 +233,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) => Price(x)).toList();
|
return resp.map((x) => x as double).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Price> getLatestPrice(String item, String variant,
|
Future<double> 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";
|
||||||
|
|
||||||
|
@ -230,31 +246,19 @@ class API {
|
||||||
|
|
||||||
var resp = jsonDecode(await getRequest(url)) as Map<String, dynamic>;
|
var resp = jsonDecode(await getRequest(url)) as Map<String, dynamic>;
|
||||||
|
|
||||||
return Price(resp);
|
return resp as double;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flows
|
// Flows
|
||||||
|
|
||||||
// /flows
|
// /flows
|
||||||
Future<Map<String, FlowInfo>> getFlows() async {
|
Map<String, FlowInfo> getFlows() {
|
||||||
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
|
||||||
Future<FlowInfo> getFlowInfo(String id) async {
|
FlowInfo getFlowInfo(String id) {
|
||||||
if (flowInfos != null) {
|
return flowInfos![id]!;
|
||||||
return flowInfos![id]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FlowInfo(jsonDecode(await getRequest("$instance/flow/$id/info")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Flow> getFlow(String id) async {
|
Future<Flow> getFlow(String id) async {
|
||||||
|
@ -277,12 +281,18 @@ class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /flow/<id>/end
|
// /flow/<id>/end
|
||||||
Future<List<String>?> endFlow(String id, {List<SupplyForm>? produced}) async {
|
Future<Map<String, List<String>>?> endFlow(String id,
|
||||||
|
{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) {
|
||||||
return (resp["produced"] as List<dynamic>).cast<String>();
|
var produced = resp["produced"] as Map<String, dynamic>;
|
||||||
|
return produced.map(
|
||||||
|
(key, value) {
|
||||||
|
return MapEntry(key, (value as List<dynamic>).cast<String>());
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -314,13 +324,8 @@ class API {
|
||||||
"$instance/transaction/$id/move", {"to": newLocation}));
|
"$instance/transaction/$id/move", {"to": newLocation}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Location> getLocation(String id) async {
|
Location getLocation(String id) {
|
||||||
if (locations != null) {
|
return locations![id]!;
|
||||||
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 {
|
||||||
|
@ -356,12 +361,14 @@ 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>{};
|
||||||
|
@ -378,6 +385,7 @@ 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"];
|
||||||
|
@ -385,26 +393,9 @@ 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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +403,7 @@ class Transaction {
|
||||||
late String uuid;
|
late String uuid;
|
||||||
late String item;
|
late String item;
|
||||||
late String variant;
|
late String variant;
|
||||||
late Price price;
|
late double price;
|
||||||
String? origin;
|
String? origin;
|
||||||
late int timestamp;
|
late int timestamp;
|
||||||
ConsumeInfo? consumed;
|
ConsumeInfo? consumed;
|
||||||
|
@ -424,7 +415,7 @@ class Transaction {
|
||||||
uuid = json["uuid"];
|
uuid = json["uuid"];
|
||||||
item = json["item"];
|
item = json["item"];
|
||||||
variant = json["variant"];
|
variant = json["variant"];
|
||||||
price = Price(json["price"]);
|
price = json["price"];
|
||||||
origin = json["origin"];
|
origin = json["origin"];
|
||||||
timestamp = json["timestamp"];
|
timestamp = json["timestamp"];
|
||||||
expired = json["expired"];
|
expired = json["expired"];
|
||||||
|
@ -433,12 +424,11 @@ class Transaction {
|
||||||
location = json["location"] != null ? Location(json["location"]) : null;
|
location = json["location"] != null ? Location(json["location"]) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction.inMemory(String itemID, String variantID, String price,
|
Transaction.inMemory(String itemID, String variantID, this.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;
|
||||||
|
@ -450,12 +440,12 @@ class Transaction {
|
||||||
|
|
||||||
class ConsumeInfo {
|
class ConsumeInfo {
|
||||||
late String destination;
|
late String destination;
|
||||||
late Price price;
|
late double price;
|
||||||
late int timestamp;
|
late int timestamp;
|
||||||
|
|
||||||
ConsumeInfo(Map<String, dynamic> json) {
|
ConsumeInfo(Map<String, dynamic> json) {
|
||||||
destination = json["destination"];
|
destination = json["destination"];
|
||||||
price = Price(json["price"]);
|
price = json["price"];
|
||||||
timestamp = json["timestamp"];
|
timestamp = json["timestamp"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -563,14 +553,14 @@ class FlowNote {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GlobalItemStat {
|
class GlobalItemStat {
|
||||||
late int item_count;
|
late int itemCount;
|
||||||
late int total_transactions;
|
late int totalTransactions;
|
||||||
late double total_price;
|
late double totalPrice;
|
||||||
|
|
||||||
GlobalItemStat(Map<String, dynamic> json) {
|
GlobalItemStat(Map<String, dynamic> json) {
|
||||||
item_count = json["item_count"];
|
itemCount = json["item_count"];
|
||||||
total_transactions = json["total_transactions"];
|
totalTransactions = json["total_transactions"];
|
||||||
total_price = json["total_price"];
|
totalPrice = json["total_price"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,3 +568,28 @@ 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"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,24 +7,53 @@ 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 StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
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() ? const MyHomePage() : const SetupPage(),
|
home: API().isInit()
|
||||||
);
|
? (API().isPrefetched()
|
||||||
|
? const MyHomePage()
|
||||||
|
: const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
))
|
||||||
|
: const SetupPage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ class _ConsumePageState extends State<ConsumePage> {
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
|
|
||||||
API()
|
API()
|
||||||
.consumeItem(
|
.consumeItem(widget.transaction.uuid, _selectedDestination,
|
||||||
widget.transaction.uuid, _selectedDestination, "$_price €")
|
double.parse(_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!')),
|
||||||
|
|
|
@ -56,7 +56,7 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirm) {
|
if (confirm ?? false) {
|
||||||
await API
|
await API
|
||||||
.API()
|
.API()
|
||||||
.endFlow(widget.flow.id)
|
.endFlow(widget.flow.id)
|
||||||
|
@ -67,23 +67,22 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
|
||||||
if (widget.info.next != null)
|
if (widget.info.next != null)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
API.API().getFlowInfo(widget.info.next!).then((newInfo) {
|
var newInfo = API.API().getFlowInfo(widget.info.next!);
|
||||||
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: EdgeInsets.symmetric(horizontal: 18.0),
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
|
|
@ -25,11 +25,10 @@ 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) {
|
||||||
API.API().getFlowInfo(flow.kind).then((info) {
|
var info = API.API().getFlowInfo(flow.kind);
|
||||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||||
builder: (context) => ActiveFlowPage(flow, info),
|
builder: (context) => ActiveFlowPage(flow, info),
|
||||||
));
|
));
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -65,26 +64,42 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildInputSelection(BuildContext context) {
|
List<Widget> buildInputSelection(BuildContext context) {
|
||||||
return Column(
|
if (widget.info.depends.isEmpty) {
|
||||||
children: widget.info.depends.map((x) {
|
return [];
|
||||||
return ElevatedButton(
|
}
|
||||||
onPressed: () {
|
|
||||||
selectDependItems(context, x);
|
return [
|
||||||
},
|
Column(
|
||||||
child: Text("Add $x"));
|
children: widget.info.depends.map((x) {
|
||||||
}).toList());
|
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() {
|
Widget flowContinuation() {
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
child: Text(widget.previousFlow!.kind),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(API.API().getFlowInfo(widget.previousFlow!.kind).name),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Text(" -> "),
|
const Card(child: Icon(Icons.arrow_right)),
|
||||||
Card(
|
Card(
|
||||||
child: Text(widget.info.name),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(widget.info.name),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -93,15 +108,17 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text("Create new ${widget.info.name} Flow")),
|
appBar: AppBar(
|
||||||
|
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
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
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';
|
||||||
|
@ -23,13 +21,7 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
API.API().getLocations().then(
|
locations = API.API().getLocations();
|
||||||
(value) {
|
|
||||||
setState(() {
|
|
||||||
locations = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
|
@ -46,25 +38,24 @@ 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: () {
|
||||||
var (itemID, variant) = API.itemVariant(i);
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
API.API().getItem(itemID).then((item) {
|
builder: (context) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
return SupplyPage(
|
||||||
builder: (context) {
|
item,
|
||||||
return SupplyPage(
|
refresh,
|
||||||
item,
|
onlyVariants: [variant],
|
||||||
refresh,
|
forcePrice: "0.00",
|
||||||
onlyVariants: [variant],
|
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
|
||||||
forcePrice: "0.00",
|
onCreate: addProduced,
|
||||||
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
|
);
|
||||||
onCreate: addProduced,
|
},
|
||||||
);
|
));
|
||||||
},
|
|
||||||
));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: Text("Produced $i")));
|
child: Text("Produced ${item.variants[variant]!.name}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -24,13 +24,17 @@ class _FlowInfoPageState extends State<FlowInfoPage> {
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// todo : ui improve
|
// todo : ui improve
|
||||||
if (widget.info.next != null) Text("Next: ${widget.info.next}"),
|
if (widget.info.next != null)
|
||||||
|
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) => Text(x)).toList(),
|
...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)
|
if (widget.info.produces?.isNotEmpty ?? false)
|
||||||
|
@ -38,7 +42,10 @@ 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) => Text(x)).toList(),
|
...widget.info.produces!.map((x) {
|
||||||
|
var (item, variant) = API.itemVariant(x);
|
||||||
|
return Text(API.API().getItem(item).variants[variant]!.name);
|
||||||
|
}).toList(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
|
@ -2,9 +2,8 @@ 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/transaction.dart';
|
import 'package:cdb_ui/pages/supply.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});
|
||||||
|
@ -20,13 +19,7 @@ class _FlowsPageState extends State<FlowsPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
API.API().getFlows().then(
|
flowInfos = API.API().getFlows();
|
||||||
(value) {
|
|
||||||
setState(() {
|
|
||||||
flowInfos = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget flowTile(BuildContext context, API.FlowInfo x) {
|
Widget flowTile(BuildContext context, API.FlowInfo x) {
|
||||||
|
@ -94,7 +87,8 @@ 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(body: flows, header: Text(key)));
|
items.add(ExpandableListItem(
|
||||||
|
body: flows, header: Text(API.API().getItem(key).name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExpandableList(items);
|
return ExpandableList(items);
|
||||||
|
@ -122,7 +116,8 @@ 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(body: flows, header: Text(key)));
|
items.add(ExpandableListItem(
|
||||||
|
body: flows, header: Text(API.API().getItem(key).name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExpandableList(items);
|
return ExpandableList(items);
|
||||||
|
@ -160,25 +155,18 @@ class _FlowsPageState extends State<FlowsPage> {
|
||||||
_ => const Text("..."),
|
_ => const Text("..."),
|
||||||
},
|
},
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// scan flow code
|
// scan flow code
|
||||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
var code = await scanQRCode(context, title: "Scan Flow Code");
|
||||||
context: context,
|
|
||||||
onCode: (code) {
|
|
||||||
// library is retarded
|
|
||||||
code = code!.replaceFirst("Code scanned = ", "");
|
|
||||||
|
|
||||||
API.API().getFlow(code).then((flow) {
|
API.API().getFlow(code!).then((flow) {
|
||||||
API.API().getFlowInfo(flow.kind).then((info) {
|
var info = API.API().getFlowInfo(flow.kind);
|
||||||
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),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,48 +1,61 @@
|
||||||
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: FutureBuilder(
|
body: ListView(
|
||||||
future: API().getItems(),
|
children: API().getItems().map((x) {
|
||||||
builder: (context, snapshot) {
|
return ItemTile(x);
|
||||||
if (!snapshot.hasData) {
|
}).toList()),
|
||||||
return const CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
var items = snapshot.data!;
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
children: items.map((x) {
|
|
||||||
return ItemTile(x);
|
|
||||||
}).toList());
|
|
||||||
}),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// scan transaction code
|
// scan transaction code
|
||||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
var code = await scanQRCode(context, title: "Scan Transaction Code");
|
||||||
context: context,
|
|
||||||
onCode: (code) {
|
|
||||||
// library is retarded
|
|
||||||
code = code!.replaceFirst("Code scanned = ", "");
|
|
||||||
|
|
||||||
API().getTransaction(code).then((t) {
|
API().getTransaction(code!).then((t) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => TransactionPage(t),
|
builder: (context) => TransactionPage(t),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.qr_code),
|
child: const Icon(Icons.qr_code),
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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)
|
||||||
|
@ -33,7 +32,7 @@ class _ItemViewState extends State<ItemView> {
|
||||||
builder: (context) => ItemStatPage(widget.item),
|
builder: (context) => ItemStatPage(widget.item),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.bar_chart))
|
icon: const Icon(Icons.bar_chart))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(children: [
|
body: Column(children: [
|
||||||
|
@ -44,12 +43,15 @@ class _ItemViewState extends State<ItemView> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Placeholder(
|
child: widget.item.image != null
|
||||||
fallbackWidth: 100,
|
? Image.network(
|
||||||
fallbackHeight: 100,
|
"${API().instance}/${widget.item.image}",
|
||||||
),
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16.0,
|
width: 16.0,
|
||||||
|
|
|
@ -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,13 +17,7 @@ class _LocationsPageState extends State<LocationsPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
API().getLocations().then(
|
locations = API().getLocations();
|
||||||
(value) {
|
|
||||||
setState(() {
|
|
||||||
locations = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode buildTree(BuildContext context, String locID) {
|
TreeNode buildTree(BuildContext context, String locID) {
|
||||||
|
@ -69,25 +63,20 @@ class _LocationsPageState extends State<LocationsPage> {
|
||||||
return buildTree(context, key);
|
return buildTree(context, key);
|
||||||
}).toList()),
|
}).toList()),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// scan location code
|
// scan location code
|
||||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
var code = await scanQRCode(context, title: "Scan Location Code");
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
if (!locations!.containsKey(code)) {
|
||||||
builder: (context) => LocationView(locations![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),
|
child: const Icon(Icons.qr_code),
|
||||||
),
|
),
|
||||||
|
@ -118,7 +107,7 @@ class _LocationViewState extends State<LocationView> {
|
||||||
title: Text(widget.location.name),
|
title: Text(widget.location.name),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
|
|
|
@ -31,6 +31,8 @@ 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,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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';
|
||||||
|
|
||||||
|
@ -23,6 +24,9 @@ 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) {
|
||||||
|
@ -45,26 +49,36 @@ class StatsPage extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconText(Icons.data_object,
|
IconText(Icons.data_object,
|
||||||
"Items: ${globalStat.item_count}"),
|
"Items: ${globalStat.itemCount}"),
|
||||||
IconText(Icons.list_alt,
|
IconText(Icons.list_alt,
|
||||||
"Inventory: ${globalStat.total_transactions}"),
|
"Inventory: ${globalStat.totalTransactions}"),
|
||||||
IconText(Icons.money,
|
IconText(Icons.money,
|
||||||
"Price: ${globalStat.total_price.toStringAsFixed(2)} €")
|
"Price: ${globalStat.totalPrice.toStringAsFixed(2)} €")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
if (min.isNotEmpty)
|
if (min.isNotEmpty)
|
||||||
const ListTile(title: Text("Items under Minimum")),
|
const ListTile(
|
||||||
|
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(
|
title: Text("$name under minimum. Needs ${item.need} more."),
|
||||||
"Item ${item.itemVariant} under minimum. Needs ${item.need} more."),
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
|
||||||
if (expired.isNotEmpty)
|
if (expired.isNotEmpty)
|
||||||
const ListTile(title: Text("Expired Items")),
|
const ListTile(
|
||||||
|
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) {
|
||||||
|
@ -85,16 +99,48 @@ 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: null,
|
body: Column(children: [
|
||||||
|
ExpandableList(item.variants.keys.map(buildVariantStat).toList())
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
import 'package:cdb_ui/api.dart';
|
import 'package:cdb_ui/api.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
import 'package:simple_barcode_scanner/enum.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;
|
||||||
|
@ -53,7 +68,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: "${_priceController.text} €",
|
price: double.parse(_priceController.text),
|
||||||
origin: _selectedOrigin,
|
origin: _selectedOrigin,
|
||||||
location: _selectedLocation,
|
location: _selectedLocation,
|
||||||
note: _noteController.text);
|
note: _noteController.text);
|
||||||
|
@ -66,8 +81,13 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
API()
|
API()
|
||||||
.supplyItem(widget.item.id, variant, "${_priceController.text} €",
|
.supplyItem(
|
||||||
_selectedOrigin, _selectedLocation, _noteController.text)
|
widget.item.id,
|
||||||
|
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!')),
|
||||||
|
@ -126,7 +146,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,
|
||||||
|
@ -138,7 +158,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.value.toStringAsFixed(2))
|
.then((x) => x.toStringAsFixed(2))
|
||||||
: _priceController.text;
|
: _priceController.text;
|
||||||
setState(() {
|
setState(() {
|
||||||
_priceController.text = price;
|
_priceController.text = price;
|
||||||
|
@ -146,11 +166,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,
|
||||||
|
@ -165,8 +185,8 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
],
|
||||||
|
|
||||||
// Location Dropdown
|
// Location Dropdown
|
||||||
Row(
|
Row(
|
||||||
|
@ -197,15 +217,13 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
width: 12,
|
width: 12,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
var code = await scanQRCode(context);
|
||||||
context: context,
|
setState(() {
|
||||||
onCode: (code) {
|
if (API().getLocations().keys.contains(code)) {
|
||||||
setState(() {
|
_selectedLocation = code!;
|
||||||
_selectedLocation = code!;
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.qr_code),
|
icon: const Icon(Icons.qr_code),
|
||||||
),
|
),
|
||||||
|
@ -235,7 +253,7 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> _fetchData() async {
|
Future<Map<String, dynamic>> _fetchData() async {
|
||||||
var locations = await API().getLocations();
|
var locations = 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();
|
||||||
|
@ -293,7 +311,7 @@ class AutocompletedTextField extends StatelessWidget {
|
||||||
class SupplyForm {
|
class SupplyForm {
|
||||||
final String itemID;
|
final String itemID;
|
||||||
final String variant;
|
final String variant;
|
||||||
final String price;
|
final double price;
|
||||||
final String? origin;
|
final String? origin;
|
||||||
final String? location;
|
final String? location;
|
||||||
final String note;
|
final String note;
|
||||||
|
|
|
@ -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,84 +40,73 @@ class _TransactionPageState extends State<TransactionPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.transaction.item),
|
title: Text(API().getItem(transaction.item).name),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
API().getLocations().then((locations) {
|
var locations = API().getLocations();
|
||||||
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((x) {
|
.then(
|
||||||
Navigator.of(context).pop();
|
(x) {
|
||||||
});
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
items: locationList
|
icon: const Icon(Icons.qr_code))
|
||||||
.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))
|
||||||
],
|
],
|
||||||
|
@ -143,12 +132,15 @@ class _TransactionPageState extends State<TransactionPage> {
|
||||||
children: [
|
children: [
|
||||||
// todo : human names
|
// todo : human names
|
||||||
Text(
|
Text(
|
||||||
"${transaction.item}",
|
API().getItem(transaction.item).name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold, fontSize: 28),
|
fontWeight: FontWeight.bold, fontSize: 28),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${transaction.variant}",
|
API()
|
||||||
|
.getItem(transaction.item)
|
||||||
|
.variants[transaction.variant]!
|
||||||
|
.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -169,7 +161,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.format(),
|
IconText(Icons.money, transaction.price.toStringAsFixed(2),
|
||||||
color: Colors.green),
|
color: Colors.green),
|
||||||
|
|
||||||
if (transaction.origin != null)
|
if (transaction.origin != null)
|
||||||
|
@ -185,7 +177,8 @@ 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(Icons.money, transaction.consumed!.price.format(),
|
IconText(
|
||||||
|
Icons.money, transaction.consumed!.price.toStringAsFixed(2),
|
||||||
color: Colors.green),
|
color: Colors.green),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -262,14 +255,14 @@ class TransactionCard extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
t.item,
|
API().getItem(t.item).name,
|
||||||
style: const TextStyle(fontSize: 16),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 4,
|
width: 8,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
t.variant,
|
API().getItem(t.item).variants[t.variant]!.name,
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
|
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -290,8 +283,7 @@ class TransactionCard extends StatelessWidget {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
IconText(Icons.money,
|
IconText(Icons.money, "${t.price.toStringAsFixed(2)} €",
|
||||||
"${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),
|
||||||
|
@ -332,6 +324,7 @@ 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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -358,29 +351,28 @@ 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
|
children: selectionList.isEmpty
|
||||||
.map((x) => TransactionCard(
|
? [
|
||||||
x,
|
const ListTile(
|
||||||
() {},
|
title: Center(child: Text("No Transactions available")),
|
||||||
onLongPress: (x) {},
|
)
|
||||||
onTap: (t) {
|
]
|
||||||
onSelect(t);
|
: selectionList
|
||||||
Navigator.of(context).pop();
|
.map((x) => TransactionCard(
|
||||||
},
|
x,
|
||||||
))
|
() {},
|
||||||
.toList()),
|
onLongPress: (x) {},
|
||||||
|
onTap: (t) {
|
||||||
|
onSelect(t);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.toList()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
pubspec.lock
138
pubspec.lock
|
@ -94,6 +94,14 @@ 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:
|
||||||
|
@ -102,6 +110,14 @@ 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:
|
||||||
|
@ -124,10 +140,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -144,30 +160,22 @@ 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: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
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: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -196,18 +204,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.15.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -240,6 +248,54 @@ 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:
|
||||||
|
@ -264,22 +320,6 @@ 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:
|
||||||
|
@ -336,6 +376,14 @@ 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
|
||||||
|
@ -385,10 +433,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -409,18 +457,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.5"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "1.1.0"
|
||||||
|
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:
|
||||||
|
@ -430,5 +486,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.5.3 <4.0.0"
|
||||||
flutter: ">=3.22.0"
|
flutter: ">=3.22.0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: cdb_ui
|
name: cdb_ui
|
||||||
description: A new Flutter project.
|
description: Economic Database.
|
||||||
# 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
|
||||||
qr_bar_code_scanner_dialog: ^0.0.5
|
simple_barcode_scanner: ^0.1.2
|
||||||
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
|
||||||
|
|
|
@ -6,9 +6,15 @@
|
||||||
|
|
||||||
#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"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue