import 'package:cdb_ui/api.dart'; import 'package:cdb_ui/pages/consume.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.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 { final Transaction transaction; final Function? refresh; const TransactionPage(this.transaction, {this.refresh, super.key}); @override State createState() => _TransactionPageState(); } class _TransactionPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.transaction.item), actions: [ IconButton( onPressed: () async { final locations = await API().getLocations(); List locationList = locations.keys.toList(); String? selectedLocationID; await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Select Location'), content: Row( mainAxisSize: MainAxisSize.min, children: [ DropdownButton( value: selectedLocationID, onChanged: (value) { selectedLocationID = value!; API() .moveTransaction(widget.transaction.uuid, selectedLocationID!) .then((x) { Navigator.of(context).pop(); }); setState(() {}); }, items: locationList .map>((locationID) { return DropdownMenuItem( value: locationID, child: Text(locations[locationID]!.name), ); }).toList(), ), IconButton( onPressed: () { 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)) ], ), body: Column( children: [ Text("UUID: ${widget.transaction.uuid}"), QRCode( data: widget.transaction.uuid, size: 22, semanticsLabel: "Transaction UUID", ), // todo : human names Text("${widget.transaction.item} - ${widget.transaction.variant}"), Text("Added: ${tsFormat(widget.transaction.timestamp)}"), if (widget.transaction.expired) const Text("Transaction is Expired!"), IconText(Icons.money, widget.transaction.price.format(), color: Colors.green), if (widget.transaction.origin != null) IconText(Icons.store, widget.transaction.origin!, color: Colors.blue), if (widget.transaction.location != null) IconText(Icons.location_city, widget.transaction.location!.name), if (widget.transaction.note != null) Text(widget.transaction.note!), if (widget.transaction.consumed != null) ...[ const Divider(), Text( "Consumed on: ${tsFormat(widget.transaction.consumed!.timestamp)}"), IconText(Icons.store, widget.transaction.consumed!.destination, color: Colors.blue), IconText(Icons.money, widget.transaction.consumed!.price.format(), color: Colors.green), ] // todo : chart with price history ], ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.of(context).pushReplacement(MaterialPageRoute( builder: (context) { return ConsumePage(widget.transaction, widget.refresh ?? () {}); }, )); }, child: const Icon(Icons.receipt_long)), ); } } class TransactionCard extends StatelessWidget { final Transaction t; final Function refresh; final Function(Transaction)? onTap; final Function(Transaction)? onLongPress; const TransactionCard(this.t, this.refresh, {this.onTap, this.onLongPress, super.key}); @override Widget build(BuildContext context) { return InkWell( onTap: () { if (onTap != null) { onTap!(t); return; } Navigator.of(context).push(MaterialPageRoute( builder: (context) { return ConsumePage(t, refresh); }, )); }, onLongPress: () { if (onLongPress != null) { onLongPress!(t); return; } Navigator.of(context).push(MaterialPageRoute( builder: (context) { return TransactionPage(t); }, )); }, child: Card( color: t.expired ? Colors.red[100] : Colors.black, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 4, child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text( t.item, style: const TextStyle(fontSize: 16), ), const SizedBox( width: 4, ), Text( t.variant, style: TextStyle(fontSize: 14, color: Colors.grey[400]), ), ], ), Text( tsFormat(t.timestamp), style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), ], ), if ((t.note ?? "").isNotEmpty) ...[ const SizedBox( height: 4, ), Text(t.note!) ], const SizedBox( height: 10, ), IconText(Icons.money, "${t.price.value.toStringAsFixed(2)} ${t.price.currency}", color: Colors.green), if (t.origin != null) ...[ const SizedBox(height: 8), IconText(Icons.store, t.origin!, color: Colors.blue) ], if (t.location != null) ...[ const SizedBox( height: 8, ), IconText(Icons.location_city, t.location!.name) ] ], ), ), ), ); } } String tsFormat(int ts) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(ts * 1000); return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); } class IconText extends StatelessWidget { final IconData icon; final String text; final Color? color; const IconText(this.icon, this.text, {super.key, this.color}); @override Widget build(BuildContext context) { return Row( children: [ Icon(icon, size: 18, color: color), const SizedBox(width: 6), Text( text, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], ); } } class TransactionSelectPage extends StatelessWidget { final Function(Transaction) onSelect; final List selections; final List? exclude; const TransactionSelectPage(this.selections, {super.key, required this.onSelect, this.exclude}); @override Widget build(BuildContext context) { var selectionList = []; for (var s in selections) { if (exclude?.any((x) => x.uuid == s.uuid) ?? false) { continue; } selectionList.add(s); } if (selectionList.isEmpty) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No Transactions to select')), ); } 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()), ); } }