diff --git a/lib/ui/add_page/view_model/add_view_model.dart b/lib/ui/add_page/view_model/add_view_model.dart index 9334ea7..3b0be3a 100644 --- a/lib/ui/add_page/view_model/add_view_model.dart +++ b/lib/ui/add_page/view_model/add_view_model.dart @@ -125,8 +125,7 @@ class AddViewModel extends ChangeNotifier { */ /// Retrieves the book associated with an ean through a [barcode] - Future> scanBook(BarcodeCapture barcode) async { - var ean = barcode.barcodes.first.rawValue!; + Future> scanBook(String ean) async { var result = await _bookRepository.getBookByEAN(ean); return result; } diff --git a/lib/ui/add_page/widgets/add_page.dart b/lib/ui/add_page/widgets/add_page.dart index 4d22005..e0e68ec 100644 --- a/lib/ui/add_page/widgets/add_page.dart +++ b/lib/ui/add_page/widgets/add_page.dart @@ -22,23 +22,20 @@ class AddPage extends StatefulWidget { } class _AddPageState extends State { - num? price; - final MobileScannerController controller = MobileScannerController( + final MobileScannerController scannerController = MobileScannerController( formats: [BarcodeFormat.ean13], detectionTimeoutMs: 1000, ); @override void dispose() { - controller.dispose(); + scannerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); - // return Consumer( - // builder: (context, screen, child) { return Scaffold( bottomNavigationBar: AppNavigationBar(startIndex: 1), body: ListenableBuilder( @@ -78,60 +75,37 @@ class _AddPageState extends State { children: [ ColoredBox(color: Colors.black), MobileScanner( - controller: controller, + controller: scannerController, onDetect: (barcodes) async { if (barcodes.barcodes.isEmpty) { return; } + if (widget.viewModel.currentOwner == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Attention : vous devez choisir un·e propriétaire", - ), - behavior: SnackBarBehavior.floating, - ), + _showMissingOwnerSnackBar( + context, + scannerController, + widget.viewModel, ); return; } - void setPrice(num newPrice) async { - setState(() { - price = newPrice; - }); - } - - Result result = await widget.viewModel.scanBook( - barcodes, + _scanEan( + context, + widget.viewModel, + barcodes.barcodes.first.rawValue!, + scannerController, ); - - switch (result) { - case Ok(): - await _confirmationDialogBuilder( - context, - setPrice, - controller, - widget.viewModel, - result.value, - ); - break; - case Error(): - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Erreur : ${result.error}"), - behavior: SnackBarBehavior.floating, - ), - ); - break; - } }, ), + Center( child: SvgPicture.asset( 'assets/scan-overlay.svg', height: (MediaQuery.sizeOf(context).height / 5) * 2, ), ), + SafeArea( child: SingleChildScrollView( child: Column( @@ -155,7 +129,7 @@ class _AddPageState extends State { ), onPressed: () => _ownerDialogBuilder( context, - controller, + scannerController, widget.viewModel, ), ), @@ -184,6 +158,7 @@ class _AddPageState extends State { ), ), ), + SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.end, @@ -197,19 +172,17 @@ class _AddPageState extends State { ), onPressed: () { if (widget.viewModel.currentOwner == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Attention : vous devez choisir un·e propriétaire", - ), - behavior: SnackBarBehavior.floating, - ), + _showMissingOwnerSnackBar( + context, + scannerController, + widget.viewModel, ); + return; } _formDialogBuilder( context, - controller, + scannerController, widget.viewModel, ); }, @@ -231,18 +204,71 @@ class _AddPageState extends State { } } +void _scanEan( + BuildContext context, + AddViewModel viewModel, + String ean, + MobileScannerController scannerController, { + Function(BuildContext)? leaveLastPopup, +}) async { + Result result = await viewModel.scanBook(ean); + + if (context.mounted) { + if (leaveLastPopup != null) { + leaveLastPopup(context); + } + switch (result) { + case Ok(): + await _confirmationDialogBuilder( + context, + scannerController, + viewModel, + result.value, + ); + break; + case Error(): + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Erreur : ${result.error}"), + behavior: SnackBarBehavior.floating, + ), + ); + break; + } + } +} + +void _showMissingOwnerSnackBar( + BuildContext context, + MobileScannerController scannerController, + AddViewModel viewModel, +) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Attention : vous devez choisir un·e propriétaire"), + duration: Duration(seconds: 4), + action: SnackBarAction( + label: "Choisir", + onPressed: () => + _ownerDialogBuilder(context, scannerController, viewModel), + ), + behavior: SnackBarBehavior.floating, + ), + ); +} + Future _confirmationDialogBuilder( BuildContext context, - Function(num) setPrice, - MobileScannerController controller, + MobileScannerController scannerController, AddViewModel viewModel, Book book, ) { - controller.stop(); + scannerController.stop(); + // Utility function to pass to downwards widgets void exitPopup(BuildContext localContext) { Navigator.of(localContext).pop(); - controller.start(); + scannerController.start(); } return showDialog( @@ -250,7 +276,6 @@ Future _confirmationDialogBuilder( barrierDismissible: false, builder: (context) => ConfirmationPopup( exitPopup: exitPopup, - setPrice: setPrice, viewModel: viewModel, book: book, ), @@ -264,6 +289,7 @@ Future _formDialogBuilder( ) { controller.stop(); + // Utility function to pass to downwards widgets void exitPopup(BuildContext localContext) { Navigator.of(localContext).pop(); controller.start(); @@ -272,7 +298,12 @@ Future _formDialogBuilder( return showDialog( context: context, barrierDismissible: false, - builder: (context) => FormPopup(viewModel: viewModel, exitPopup: exitPopup), + builder: (context) => FormPopup( + viewModel: viewModel, + exitPopup: exitPopup, + scannerController: controller, + scanEan: _scanEan, + ), ); } @@ -283,7 +314,8 @@ Future _ownerDialogBuilder( ) { controller.stop(); - void onPressAccept(BuildContext localContext) { + // Utility function to pass to downwards widgets + void exitPopup(BuildContext localContext) { Navigator.of(localContext).pop(); controller.start(); } @@ -292,6 +324,6 @@ Future _ownerDialogBuilder( context: context, barrierDismissible: false, builder: (context) => - OwnerPopup(viewModel: viewModel, onPressAccept: onPressAccept), + OwnerPopup(viewModel: viewModel, exitPopup: exitPopup), ); } diff --git a/lib/ui/add_page/widgets/confirmation_popup.dart b/lib/ui/add_page/widgets/confirmation_popup.dart index e57f02c..998e3ea 100644 --- a/lib/ui/add_page/widgets/confirmation_popup.dart +++ b/lib/ui/add_page/widgets/confirmation_popup.dart @@ -7,13 +7,11 @@ class ConfirmationPopup extends StatefulWidget { const ConfirmationPopup({ super.key, required this.exitPopup, - required this.setPrice, required this.viewModel, required this.book, }); final Function(BuildContext) exitPopup; - final Function(num) setPrice; final AddViewModel viewModel; final Book book; @@ -24,9 +22,11 @@ class ConfirmationPopup extends StatefulWidget { class _ConfirmationPopupState extends State { final GlobalKey _formKey = GlobalKey(); double price = 0; + @override Widget build(BuildContext context) { final theme = Theme.of(context); + return AlertDialog( title: Text("Prix"), content: Form( @@ -72,6 +72,7 @@ class _ConfirmationPopupState extends State { ], ), ), + SizedBox(height: 10), (widget.viewModel.askPrice) ? TextFormField( @@ -87,15 +88,21 @@ class _ConfirmationPopupState extends State { validator: (value) { if (value == null || value.isEmpty) { return "Indiquez un prix"; - } else if (num.tryParse(value) == null) { + } else if (double.tryParse( + value.replaceAll(",", "."), + ) == + null) { return "Le prix doit être un nombre"; - } else if (num.parse(value) < 0) { + } else if (double.parse(value.replaceAll(",", ".")) < + 0) { return "Le prix doit être positif ou nul"; } return null; }, onSaved: (newValue) { - price = double.parse(newValue!); + price = double.parse( + newValue?.replaceAll(",", ".") ?? "0", + ); }, ) : SizedBox(), @@ -105,6 +112,7 @@ class _ConfirmationPopupState extends State { ), actions: [ TextButton( + child: Text("Annuler"), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -114,13 +122,15 @@ class _ConfirmationPopupState extends State { ); widget.exitPopup(context); }, - child: Text("Annuler"), ), TextButton( + child: Text("Valider"), onPressed: () async { if (widget.viewModel.askPrice && _formKey.currentState!.validate()) { _formKey.currentState!.save(); + } else { + return; } var result = await widget.viewModel.sendNewBookInstance( @@ -136,49 +146,61 @@ class _ConfirmationPopupState extends State { Navigator.of(context).pop(); showDialog( context: context, - builder: (context) => AlertDialog( - title: Text( - "ID : ${widget.viewModel.currentOwner!.firstName[0].toUpperCase()}${widget.viewModel.currentOwner!.lastName[0].toUpperCase()}${(price == 0) ? "PL" : price.toString()}", - ), - content: Text( - (widget.viewModel.currentOwner!.id == - widget.viewModel.ownerOfUser!.id) - ? "Ce livre appartient à la section. Vous pouvez mettre le code, ou poser une gomette, ..." - : "Identifiant propriétaire de ce livre. Pensez à l'écrire pour retrouver lae propriétaire du livre lors de la vente ou du retour !", - ), - actions: [ - TextButton( - onPressed: () { - widget.exitPopup(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Enregistré: ${widget.book.title}", - ), - behavior: SnackBarBehavior.floating, - ), - ); - }, - child: Text("Ok"), - ), - ], - ), + barrierDismissible: false, + builder: (context) => + RegisteredBookPopup(widget: widget, price: price), ); } break; case Error(): if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Une erreur est survenue : ${result.error}", - ), - ), + SnackBar(content: Text("Erreur : ${result.error}")), ); } } }, - child: Text("Valider"), + ), + ], + ); + } +} + +class RegisteredBookPopup extends StatelessWidget { + const RegisteredBookPopup({ + super.key, + required this.widget, + required this.price, + }); + + final ConfirmationPopup widget; + final double price; + + @override + Widget build(BuildContext context) { + return AlertDialog( + // This thing is the BookInstance's short ID + title: Text( + "ID : ${widget.viewModel.currentOwner!.firstName[0].toUpperCase()}${widget.viewModel.currentOwner!.lastName[0].toUpperCase()}${(price == 0) ? "PL" : price.toString()}", + ), + content: Text( + (widget.viewModel.currentOwner!.id == widget.viewModel.ownerOfUser!.id) + ? "Pensez à la gomette ! Ce livre appartient au syndicat." + : "Identifiant propriétaire de ce livre. Pensez à l'écrire pour retrouver lae propriétaire du livre lors de la vente ou du retour !", + ), + actions: [ + TextButton( + child: Text("Ok"), + onPressed: () { + widget.exitPopup(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Livre enregistré: ${widget.book.title}"), + behavior: SnackBarBehavior.floating, + duration: Duration(seconds: 2), + ), + ); + }, ), ], ); diff --git a/lib/ui/add_page/widgets/form_popup.dart b/lib/ui/add_page/widgets/form_popup.dart index 2a3f1de..47a3104 100644 --- a/lib/ui/add_page/widgets/form_popup.dart +++ b/lib/ui/add_page/widgets/form_popup.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:seshat/ui/add_page/view_model/add_view_model.dart'; class FormPopup extends StatelessWidget { @@ -6,10 +7,14 @@ class FormPopup extends StatelessWidget { super.key, required this.viewModel, required this.exitPopup, + required this.scannerController, + required this.scanEan, }); final AddViewModel viewModel; final Function(BuildContext) exitPopup; + final MobileScannerController scannerController; + final Function scanEan; @override Widget build(BuildContext context) { @@ -31,6 +36,8 @@ class FormPopup extends StatelessWidget { return _ManualEANPopup( exitPopup: exitPopup, viewModel: viewModel, + scannerController: scannerController, + scanEan: scanEan, ); }, ); @@ -51,6 +58,7 @@ class FormPopup extends StatelessWidget { ), ), ), + Card( clipBehavior: Clip.hardEdge, child: InkWell( @@ -93,11 +101,24 @@ class FormPopup extends StatelessWidget { } } +/* + * ====================== + * ====< MANUAL EAN >==== + * ====================== +*/ + class _ManualEANPopup extends StatefulWidget { - const _ManualEANPopup({required this.exitPopup, required this.viewModel}); + const _ManualEANPopup({ + required this.exitPopup, + required this.viewModel, + required this.scannerController, + required this.scanEan, + }); final Function(BuildContext) exitPopup; final AddViewModel viewModel; + final MobileScannerController scannerController; + final Function scanEan; @override State<_ManualEANPopup> createState() => _ManualEANPopupState(); @@ -106,11 +127,11 @@ class _ManualEANPopup extends StatefulWidget { class _ManualEANPopupState extends State<_ManualEANPopup> { final GlobalKey _formKey = GlobalKey(); String? ean; - num? price; + @override Widget build(BuildContext context) { return AlertDialog( - title: Text("Recherche par EAN"), + title: Text("Entrée manuelle par EAN"), content: Form( key: _formKey, child: Column( @@ -128,61 +149,39 @@ class _ManualEANPopupState extends State<_ManualEANPopup> { validator: (value) { if (value == null || value.length != 13 || - int.tryParse(value) == null) { + int.tryParse(value) == null || + int.parse(value) < 0) { return "L'entrée n'est pas un code EAN-13 valide"; } return null; }, ), - SizedBox(height: 10), - ListenableBuilder( - listenable: widget.viewModel, - builder: (context, child) { - return (widget.viewModel.askPrice) - ? TextFormField( - decoration: InputDecoration( - labelText: "Prix", - border: OutlineInputBorder(), - suffixText: "€", - ), - keyboardType: TextInputType.numberWithOptions( - decimal: true, - ), - validator: (value) { - if (value == null || value.isEmpty) { - return "Indiquez un prix"; - } else if (num.tryParse(value) == null) { - return "Le prix doit être un nombre"; - } else if (num.parse(value) < 0) { - return "Le prix doit être positif ou nul"; - } - return null; - }, - onSaved: (newValue) { - price = num.parse(newValue!); - }, - ) - : SizedBox(); - }, - ), ], ), ), actions: [ TextButton( + child: Text("Annuler"), onPressed: () { widget.exitPopup(context); }, - child: Text("Annuler"), ), TextButton( + child: Text("Valider"), onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); - widget.exitPopup(context); + widget.scanEan( + context, + widget.viewModel, + ean!, + widget.scannerController, + leaveLastPopup: (context) { + Navigator.of(context).pop(); + }, + ); } }, - child: Text("Valider"), ), ], ); diff --git a/lib/ui/add_page/widgets/owner_popup.dart b/lib/ui/add_page/widgets/owner_popup.dart index 35fe4b3..8627c13 100644 --- a/lib/ui/add_page/widgets/owner_popup.dart +++ b/lib/ui/add_page/widgets/owner_popup.dart @@ -6,11 +6,11 @@ class OwnerPopup extends StatefulWidget { const OwnerPopup({ super.key, required this.viewModel, - required this.onPressAccept, + required this.exitPopup, }); final AddViewModel viewModel; - final Function(BuildContext) onPressAccept; + final Function(BuildContext) exitPopup; @override State createState() => _OwnerPopupState(); @@ -166,7 +166,7 @@ class _OwnerPopupState extends State { }); } } - widget.onPressAccept(context); + widget.exitPopup(context); }, child: Text( (!showNewOwner && searchController.text == "")