This commit is contained in:
JMARyA 2024-09-24 18:00:13 +02:00
parent 710ea6743b
commit b33e65706b
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
8 changed files with 489 additions and 472 deletions

View file

@ -1,5 +1,5 @@
import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/flow.dart';
import 'package:cdb_ui/pages/flow/flows_page.dart';
import 'package:cdb_ui/pages/items.dart';
import 'package:cdb_ui/pages/locations.dart';
import 'package:cdb_ui/pages/setup.dart';

View file

@ -1,471 +0,0 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/expandable_list.dart';
import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart';
class FlowsPage extends StatefulWidget {
const FlowsPage({super.key});
@override
State<FlowsPage> createState() => _FlowsPageState();
}
class _FlowsPageState extends State<FlowsPage> {
int tabSelection = 0;
Map<String, API.FlowInfo>? flowInfos;
@override
void initState() {
super.initState();
API.API().getFlows().then(
(value) {
setState(() {
flowInfos = value;
});
},
);
}
Widget flowTile(BuildContext context, API.FlowInfo x) {
return ListTile(
title: Text(x.name),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FlowInfoPage(x),
));
},
);
}
Widget listFlowInfoByActive(
BuildContext context, Map<String, API.FlowInfo> infos) {
return FutureBuilder(future: () async {
var included = [];
for (var key in infos.keys) {
var active = await API.API().getActiveFlowsOf(key);
if (active.isNotEmpty) {
included.add(infos[key]);
}
}
return included;
}(), builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
var included = snapshot.data!;
return ListView(
children: included.map((x) => flowTile(context, x)).toList());
});
}
Widget listAllFlowInfos(
BuildContext context, Map<String, API.FlowInfo> infos) {
return ListView(
children: infos.values.map((x) {
return flowTile(context, x);
}).toList());
}
Widget listFlowInfoByProduced(
BuildContext context, Map<String, API.FlowInfo> infos) {
Map<String, List<API.FlowInfo>> producedMapping = {};
for (var f in infos.values) {
for (var produces in f.produces ?? []) {
var item = API.itemVariant(produces).$1;
producedMapping.putIfAbsent(item, () {
return [];
});
producedMapping[item]!.add(f);
}
}
List<ExpandableListItem> items = [];
for (var key in producedMapping.keys) {
var flows = Column(
children: producedMapping[key]!.map((x) {
return flowTile(context, x);
}).toList());
items.add(ExpandableListItem(body: flows, header: Text(key)));
}
return ExpandableList(items);
}
Widget listFlowInfoByDependant(
BuildContext context, Map<String, API.FlowInfo> infos) {
Map<String, List<API.FlowInfo>> dependsMapping = {};
for (var f in infos.values) {
for (var produces in f.depends) {
var item = API.itemVariant(produces).$1;
// todo : add only if item is in inventory
dependsMapping.putIfAbsent(item, () {
return [];
});
dependsMapping[item]!.add(f);
}
}
List<ExpandableListItem> items = [];
for (var key in dependsMapping.keys) {
var flows = Column(
children: dependsMapping[key]!.map((x) {
return flowTile(context, x);
}).toList());
items.add(ExpandableListItem(body: flows, header: Text(key)));
}
return ExpandableList(items);
}
@override
Widget build(BuildContext context) {
if (flowInfos == null) {
return const CircularProgressIndicator();
}
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: const Text("Flows"),
bottom: TabBar(
tabs: const [
Tab(text: "All"),
Tab(text: "Produces"),
Tab(text: "Depends"),
Tab(text: "Active")
],
onTap: (value) {
setState(() {
tabSelection = value;
});
},
)),
body: switch (tabSelection) {
0 => listAllFlowInfos(context, flowInfos!),
1 => listFlowInfoByProduced(context, flowInfos!),
2 => listFlowInfoByDependant(context, flowInfos!),
3 => listFlowInfoByActive(context, flowInfos!),
_ => const Text("..."),
}));
}
}
class FlowInfoPage extends StatefulWidget {
final API.FlowInfo info;
const FlowInfoPage(this.info, {super.key});
@override
State<FlowInfoPage> createState() => _FlowInfoPageState();
}
class _FlowInfoPageState extends State<FlowInfoPage> {
void refresh() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.info.name)),
body: Column(
children: [
// todo : ui improve
if (widget.info.next != null) Text("Next: ${widget.info.next}"),
if (widget.info.depends.isNotEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text("Flow can use: "),
...widget.info.depends.map((x) => Text(x)).toList(),
],
),
if (widget.info.produces?.isNotEmpty ?? false)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text("Flow can produce: "),
...widget.info.produces!.map((x) => Text(x)).toList(),
],
),
const Divider(),
FutureBuilder(
future: API.API().getActiveFlowsOf(widget.info.id),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
var data = snapshot.data!;
return Expanded(
child: ListView(
children: data
.map((x) => ListTile(
title: Text(x.id),
onTap: () =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
ActiveFlowPage(x, widget.info),
))))
.toList()),
);
},
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CreateFlowPage(widget.info, refresh)));
},
child: const Icon(Icons.add)),
);
}
}
class CreateFlowPage extends StatefulWidget {
final API.FlowInfo info;
final Function refresh;
final API.Flow? previousFlow;
const CreateFlowPage(this.info, this.refresh, {this.previousFlow, super.key});
@override
State<CreateFlowPage> createState() => _CreateFlowPageState();
}
class _CreateFlowPageState extends State<CreateFlowPage> {
List<API.Transaction> depends = [];
void _create(BuildContext context) {
if (widget.previousFlow != null) {
API
.API()
.continueFlow(widget.previousFlow!.id,
input: depends.map((x) => x.uuid).toList())
.then((x) {
API.API().getFlow(x).then((flow) {
API.API().getFlowInfo(flow.kind).then((info) {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ActiveFlowPage(flow, info),
));
});
});
});
return;
}
API
.API()
.startFlow(widget.info.id, input: depends.map((x) => x.uuid).toList())
.then((flowID) {
widget.refresh();
API.API().getFlow(flowID).then((flow) {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ActiveFlowPage(flow, widget.info)));
});
});
}
void selectDependItems(BuildContext context, String itemVariant) {
var (item, variant) = API.itemVariant(itemVariant);
API.API().getInventoryOfVariant(item, variant).then((transactions) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return TransactionSelectPage(transactions, onSelect: (t) {
if (!depends.contains(t)) {
setState(() {
depends.add(t);
});
}
}, exclude: depends);
},
));
});
}
Widget buildInputSelection(BuildContext context) {
return Column(
children: widget.info.depends.map((x) {
return ElevatedButton(
onPressed: () {
selectDependItems(context, x);
},
child: Text("Add $x"));
}).toList());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Create new ${widget.info.name} Flow")),
body: Column(
children: [
// todo : flow continuation overview
buildInputSelection(context),
const Divider(),
Card(
child: Column(
children: depends
.map((x) => TransactionCard(x, () {},
onTap: (x) {}, onLongPress: (x) {}))
.toList())),
ElevatedButton(
onPressed: () => _create(context),
child: const Text("Create Flow"))
],
),
);
}
}
class TransactionSelectPage extends StatelessWidget {
final Function(API.Transaction) onSelect;
final List<API.Transaction> selections;
final List<API.Transaction>? exclude;
const TransactionSelectPage(this.selections,
{super.key, required this.onSelect, this.exclude});
@override
Widget build(BuildContext context) {
var selectionList = [];
for (var s in selections) {
if (exclude?.any((x) => x.uuid == s.uuid) ?? false) {
continue;
}
selectionList.add(s);
}
return Scaffold(
appBar: AppBar(
title: const Text("Select a Transaction"),
),
body: ListView(
children: selections
.map((x) => TransactionCard(
x,
() {},
onLongPress: (x) {},
onTap: (t) {
onSelect(t);
Navigator.of(context).pop();
},
))
.toList()),
);
}
}
class EndFlowWithProduce extends StatelessWidget {
final API.Flow flow;
final API.FlowInfo info;
const EndFlowWithProduce(this.flow, this.info, {super.key});
@override
Widget build(BuildContext context) {
// todo : show end screen with produce
return Scaffold();
}
}
class ActiveFlowPage extends StatelessWidget {
final API.Flow flow;
final API.FlowInfo info;
const ActiveFlowPage(this.flow, this.info, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(info.name),
actions: [
IconButton(
onPressed: () async {
if (info.produces?.isNotEmpty ?? false) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EndFlowWithProduce(flow, info)));
return;
}
// simple dialog
var confirm = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Are you sure?'),
content: const Text('This will end the flow.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('End'),
),
],
),
);
if (confirm) {
await API
.API()
.endFlow(flow.id)
.then((x) => Navigator.of(context).pop());
}
},
icon: const Icon(Icons.stop)),
// todo : continue next flow
if (info.next != null)
IconButton(
onPressed: () {
API.API().getFlowInfo(info.next!).then((newInfo) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return CreateFlowPage(
newInfo,
() {},
previousFlow: flow,
);
},
));
});
},
icon: const Icon(Icons.arrow_forward)),
// todo : add notes to flow
IconButton(onPressed: () {}, icon: const Icon(Icons.note))
],
),
body: Column(
children: [
Text("ID: ${flow.id}"),
Text("Started since: ${tsFormat(flow.started)}"),
...flow.input!.map((x) => Text("Input: $x")).toList(),
if (flow.done != null) ...[
Text("Ended: ${tsFormat(flow.done!.ended)}"),
if (flow.done!.next != null) Text("Next: ${flow.done!.next!}"),
...flow.done!.produced!.map((x) => Text("Produced: $x")).toList(),
],
const Divider(),
// todo : show notes
],
));
}
}

View file

@ -0,0 +1,92 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/flow/create_flow_page.dart';
import 'package:cdb_ui/pages/flow/end_flow_page.dart';
import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart';
class ActiveFlowPage extends StatelessWidget {
final API.Flow flow;
final API.FlowInfo info;
const ActiveFlowPage(this.flow, this.info, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(info.name),
actions: [
IconButton(
onPressed: () async {
if (info.produces?.isNotEmpty ?? false) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EndFlowWithProduce(flow, info)));
return;
}
// simple dialog
var confirm = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Are you sure?'),
content: const Text('This will end the flow.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('End'),
),
],
),
);
if (confirm) {
await API
.API()
.endFlow(flow.id)
.then((x) => Navigator.of(context).pop());
}
},
icon: const Icon(Icons.stop)),
// todo : continue next flow
if (info.next != null)
IconButton(
onPressed: () {
API.API().getFlowInfo(info.next!).then((newInfo) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return CreateFlowPage(
newInfo,
() {},
previousFlow: flow,
);
},
));
});
},
icon: const Icon(Icons.arrow_forward)),
// todo : add notes to flow
IconButton(onPressed: () {}, icon: const Icon(Icons.note))
],
),
body: Column(
children: [
Text("ID: ${flow.id}"),
Text("Started since: ${tsFormat(flow.started)}"),
...flow.input!.map((x) => Text("Input: $x")).toList(),
if (flow.done != null) ...[
Text("Ended: ${tsFormat(flow.done!.ended)}"),
if (flow.done!.next != null) Text("Next: ${flow.done!.next!}"),
...flow.done!.produced!.map((x) => Text("Produced: $x")).toList(),
],
const Divider(),
// todo : show notes
],
));
}
}

View file

@ -0,0 +1,102 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/flow/active_flow_page.dart';
import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart';
class CreateFlowPage extends StatefulWidget {
final API.FlowInfo info;
final Function refresh;
final API.Flow? previousFlow;
const CreateFlowPage(this.info, this.refresh, {this.previousFlow, super.key});
@override
State<CreateFlowPage> createState() => _CreateFlowPageState();
}
class _CreateFlowPageState extends State<CreateFlowPage> {
List<API.Transaction> depends = [];
void _create(BuildContext context) {
if (widget.previousFlow != null) {
API
.API()
.continueFlow(widget.previousFlow!.id,
input: depends.map((x) => x.uuid).toList())
.then((x) {
API.API().getFlow(x).then((flow) {
API.API().getFlowInfo(flow.kind).then((info) {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ActiveFlowPage(flow, info),
));
});
});
});
return;
}
API
.API()
.startFlow(widget.info.id, input: depends.map((x) => x.uuid).toList())
.then((flowID) {
widget.refresh();
API.API().getFlow(flowID).then((flow) {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ActiveFlowPage(flow, widget.info)));
});
});
}
void selectDependItems(BuildContext context, String itemVariant) {
var (item, variant) = API.itemVariant(itemVariant);
API.API().getInventoryOfVariant(item, variant).then((transactions) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return TransactionSelectPage(transactions, onSelect: (t) {
if (!depends.contains(t)) {
setState(() {
depends.add(t);
});
}
}, exclude: depends);
},
));
});
}
Widget buildInputSelection(BuildContext context) {
return Column(
children: widget.info.depends.map((x) {
return ElevatedButton(
onPressed: () {
selectDependItems(context, x);
},
child: Text("Add $x"));
}).toList());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Create new ${widget.info.name} Flow")),
body: Column(
children: [
// todo : flow continuation overview
buildInputSelection(context),
const Divider(),
Card(
child: Column(
children: depends
.map((x) => TransactionCard(x, () {},
onTap: (x) {}, onLongPress: (x) {}))
.toList())),
ElevatedButton(
onPressed: () => _create(context),
child: const Text("Create Flow"))
],
),
);
}
}

View file

@ -0,0 +1,15 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:flutter/material.dart';
class EndFlowWithProduce extends StatelessWidget {
final API.Flow flow;
final API.FlowInfo info;
const EndFlowWithProduce(this.flow, this.info, {super.key});
@override
Widget build(BuildContext context) {
// todo : show end screen with produce
return Scaffold();
}
}

View file

@ -0,0 +1,78 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/flow/active_flow_page.dart';
import 'package:cdb_ui/pages/flow/create_flow_page.dart';
import 'package:flutter/material.dart';
class FlowInfoPage extends StatefulWidget {
final API.FlowInfo info;
const FlowInfoPage(this.info, {super.key});
@override
State<FlowInfoPage> createState() => _FlowInfoPageState();
}
class _FlowInfoPageState extends State<FlowInfoPage> {
void refresh() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.info.name)),
body: Column(
children: [
// todo : ui improve
if (widget.info.next != null) Text("Next: ${widget.info.next}"),
if (widget.info.depends.isNotEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text("Flow can use: "),
...widget.info.depends.map((x) => Text(x)).toList(),
],
),
if (widget.info.produces?.isNotEmpty ?? false)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text("Flow can produce: "),
...widget.info.produces!.map((x) => Text(x)).toList(),
],
),
const Divider(),
FutureBuilder(
future: API.API().getActiveFlowsOf(widget.info.id),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
var data = snapshot.data!;
return Expanded(
child: ListView(
children: data
.map((x) => ListTile(
title: Text(x.id),
onTap: () =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
ActiveFlowPage(x, widget.info),
))))
.toList()),
);
},
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CreateFlowPage(widget.info, refresh)));
},
child: const Icon(Icons.add)),
);
}
}

View file

@ -0,0 +1,161 @@
import 'package:cdb_ui/api.dart' as API;
import 'package:cdb_ui/pages/expandable_list.dart';
import 'package:cdb_ui/pages/flow/flow_info_page.dart';
import 'package:cdb_ui/pages/transaction.dart';
import 'package:flutter/material.dart';
class FlowsPage extends StatefulWidget {
const FlowsPage({super.key});
@override
State<FlowsPage> createState() => _FlowsPageState();
}
class _FlowsPageState extends State<FlowsPage> {
int tabSelection = 0;
Map<String, API.FlowInfo>? flowInfos;
@override
void initState() {
super.initState();
API.API().getFlows().then(
(value) {
setState(() {
flowInfos = value;
});
},
);
}
Widget flowTile(BuildContext context, API.FlowInfo x) {
return ListTile(
title: Text(x.name),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FlowInfoPage(x),
));
},
);
}
Widget listFlowInfoByActive(
BuildContext context, Map<String, API.FlowInfo> infos) {
return FutureBuilder(future: () async {
var included = [];
for (var key in infos.keys) {
var active = await API.API().getActiveFlowsOf(key);
if (active.isNotEmpty) {
included.add(infos[key]);
}
}
return included;
}(), builder: (context, snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
var included = snapshot.data!;
return ListView(
children: included.map((x) => flowTile(context, x)).toList());
});
}
Widget listAllFlowInfos(
BuildContext context, Map<String, API.FlowInfo> infos) {
return ListView(
children: infos.values.map((x) {
return flowTile(context, x);
}).toList());
}
Widget listFlowInfoByProduced(
BuildContext context, Map<String, API.FlowInfo> infos) {
Map<String, List<API.FlowInfo>> producedMapping = {};
for (var f in infos.values) {
for (var produces in f.produces ?? []) {
var item = API.itemVariant(produces).$1;
producedMapping.putIfAbsent(item, () {
return [];
});
producedMapping[item]!.add(f);
}
}
List<ExpandableListItem> items = [];
for (var key in producedMapping.keys) {
var flows = Column(
children: producedMapping[key]!.map((x) {
return flowTile(context, x);
}).toList());
items.add(ExpandableListItem(body: flows, header: Text(key)));
}
return ExpandableList(items);
}
Widget listFlowInfoByDependant(
BuildContext context, Map<String, API.FlowInfo> infos) {
Map<String, List<API.FlowInfo>> dependsMapping = {};
for (var f in infos.values) {
for (var produces in f.depends) {
var item = API.itemVariant(produces).$1;
// todo : add only if item is in inventory
dependsMapping.putIfAbsent(item, () {
return [];
});
dependsMapping[item]!.add(f);
}
}
List<ExpandableListItem> items = [];
for (var key in dependsMapping.keys) {
var flows = Column(
children: dependsMapping[key]!.map((x) {
return flowTile(context, x);
}).toList());
items.add(ExpandableListItem(body: flows, header: Text(key)));
}
return ExpandableList(items);
}
@override
Widget build(BuildContext context) {
if (flowInfos == null) {
return const CircularProgressIndicator();
}
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: const Text("Flows"),
bottom: TabBar(
tabs: const [
Tab(text: "All"),
Tab(text: "Produces"),
Tab(text: "Depends"),
Tab(text: "Active")
],
onTap: (value) {
setState(() {
tabSelection = value;
});
},
)),
body: switch (tabSelection) {
0 => listAllFlowInfos(context, flowInfos!),
1 => listFlowInfoByProduced(context, flowInfos!),
2 => listFlowInfoByDependant(context, flowInfos!),
3 => listFlowInfoByActive(context, flowInfos!),
_ => const Text("..."),
}));
}
}

View file

@ -281,3 +281,43 @@ class IconText extends StatelessWidget {
);
}
}
class TransactionSelectPage extends StatelessWidget {
final Function(Transaction) onSelect;
final List<Transaction> selections;
final List<Transaction>? exclude;
const TransactionSelectPage(this.selections,
{super.key, required this.onSelect, this.exclude});
@override
Widget build(BuildContext context) {
var selectionList = [];
for (var s in selections) {
if (exclude?.any((x) => x.uuid == s.uuid) ?? false) {
continue;
}
selectionList.add(s);
}
return Scaffold(
appBar: AppBar(
title: const Text("Select a Transaction"),
),
body: ListView(
children: selectionList
.map((x) => TransactionCard(
x,
() {},
onLongPress: (x) {},
onTap: (t) {
onSelect(t);
Navigator.of(context).pop();
},
))
.toList()),
);
}
}