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 createState() => _FlowsPageState(); } class _FlowsPageState extends State { int tabSelection = 0; Map? 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 infos) { return FutureBuilder(future: () async { var included = []; for (var key in infos.keys) { var active = await API.API().getActiveFlowsOf(key); if (active.isNotEmpty) { included.add(infos[key]); } } return included; }(), builder: (context, snapshot) { if (!snapshot.hasData) { return const CircularProgressIndicator(); } var included = snapshot.data!; return ListView( children: included.map((x) => flowTile(context, x)).toList()); }); } Widget listAllFlowInfos( BuildContext context, Map infos) { return ListView( children: infos.values.map((x) { return flowTile(context, x); }).toList()); } Widget listFlowInfoByProduced( BuildContext context, Map infos) { Map> producedMapping = {}; for (var f in infos.values) { for (var produces in f.produces ?? []) { var item = API.itemVariant(produces).$1; producedMapping.putIfAbsent(item, () { return []; }); producedMapping[item]!.add(f); } } List items = []; for (var key in producedMapping.keys) { var flows = Column( children: producedMapping[key]!.map((x) { return flowTile(context, x); }).toList()); items.add(ExpandableListItem(body: flows, header: Text(key))); } return ExpandableList(items); } Widget listFlowInfoByDependant( BuildContext context, Map infos) { Map> dependsMapping = {}; for (var f in infos.values) { for (var produces in f.depends) { var item = API.itemVariant(produces).$1; // todo : add only if item is in inventory dependsMapping.putIfAbsent(item, () { return []; }); dependsMapping[item]!.add(f); } } List items = []; for (var key in dependsMapping.keys) { var flows = Column( children: dependsMapping[key]!.map((x) { return flowTile(context, x); }).toList()); items.add(ExpandableListItem(body: flows, header: Text(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 createState() => _FlowInfoPageState(); } class _FlowInfoPageState extends State { void refresh() { setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.info.name)), body: Column( children: [ // todo : ui improve if (widget.info.next != null) Text("Next: ${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 createState() => _CreateFlowPageState(); } class _CreateFlowPageState extends State { List depends = []; void _create(BuildContext context) { if (widget.previousFlow != null) { API .API() .continueFlow(widget.previousFlow!.id, input: depends.map((x) => x.uuid).toList()) .then((x) { API.API().getFlow(x).then((flow) { 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 selections; final List? exclude; const TransactionSelectPage(this.selections, {super.key, required this.onSelect, this.exclude}); @override Widget build(BuildContext context) { var selectionList = []; for (var s in selections) { if (exclude?.any((x) => x.uuid == s.uuid) ?? false) { continue; } selectionList.add(s); } return Scaffold( appBar: AppBar( title: const Text("Select a Transaction"), ), body: ListView( children: selections .map((x) => TransactionCard( x, () {}, onLongPress: (x) {}, onTap: (t) { onSelect(t); Navigator.of(context).pop(); }, )) .toList()), ); } } 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: [ // todo : end flow IconButton( onPressed: () async { if (info.produces?.isNotEmpty ?? false) { // todo : show end screen with produce return; } // simple dialog var confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Are you sure?'), content: const Text('This will delete the flow.'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('Delete'), ), ], ), ); 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 ], )); } }