fix: flow on scan + owners managment
This commit is contained in:
parent
70146055df
commit
86094b5d76
10 changed files with 283 additions and 130 deletions
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
15
lib/domain/models/book.dart
Normal file
15
lib/domain/models/book.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
class Book {
|
||||||
|
Book({
|
||||||
|
required this.author,
|
||||||
|
required this.ean,
|
||||||
|
required this.id,
|
||||||
|
required this.priceNew,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
String author;
|
||||||
|
String title;
|
||||||
|
String ean;
|
||||||
|
int id;
|
||||||
|
String priceNew;
|
||||||
|
}
|
||||||
28
lib/domain/models/book_instance.dart
Normal file
28
lib/domain/models/book_instance.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
class BookInstance {
|
||||||
|
BookInstance({
|
||||||
|
required this.balId,
|
||||||
|
required this.bookId,
|
||||||
|
required this.id,
|
||||||
|
required this.ownerId,
|
||||||
|
required this.price,
|
||||||
|
required this.status,
|
||||||
|
this.soldPrice,
|
||||||
|
});
|
||||||
|
|
||||||
|
int balId;
|
||||||
|
int bookId;
|
||||||
|
int id;
|
||||||
|
int ownerId;
|
||||||
|
double price;
|
||||||
|
double? soldPrice;
|
||||||
|
bool status;
|
||||||
|
|
||||||
|
factory BookInstance.fromJSON(Map<String, dynamic> json) => BookInstance(
|
||||||
|
balId: json["balId"],
|
||||||
|
bookId: json["bookId"],
|
||||||
|
id: json["id"],
|
||||||
|
ownerId: json["ownerId"],
|
||||||
|
price: json["price"],
|
||||||
|
status: json["status"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
class Owner {
|
||||||
|
|
||||||
class Owner extends ChangeNotifier {
|
|
||||||
Owner({
|
Owner({
|
||||||
required this.firstName,
|
required this.firstName,
|
||||||
required this.lastName,
|
required this.lastName,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:seshat/config/dependencies.dart';
|
|
||||||
import 'package:seshat/routing/router.dart';
|
import 'package:seshat/routing/router.dart';
|
||||||
|
|
||||||
// TODO: In router, make it so that the navbar is integrated in everyscreen that needs it -> consistancy (should be at least, hope so) so it stops jumping. Then, make it so that the pages are children of scaffold. That's all ?
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import 'package:seshat/domain/models/book.dart';
|
||||||
|
import 'package:seshat/domain/models/book_instance.dart';
|
||||||
import 'package:seshat/domain/models/owner.dart';
|
import 'package:seshat/domain/models/owner.dart';
|
||||||
|
import 'package:seshat/utils/result.dart';
|
||||||
|
|
||||||
class AddViewModel extends ChangeNotifier {
|
class AddViewModel extends ChangeNotifier {
|
||||||
AddViewModel();
|
AddViewModel();
|
||||||
|
|
@ -15,44 +19,44 @@ class AddViewModel extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Owner>? _owners = [
|
List<Owner> _owners = [];
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Jean",
|
// firstName: "Jean",
|
||||||
lastName: "Henri",
|
// lastName: "Henri",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 1,
|
// id: 1,
|
||||||
),
|
// ),
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Jeanette",
|
// firstName: "Jeanette",
|
||||||
lastName: "Henriette",
|
// lastName: "Henriette",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 2,
|
// id: 2,
|
||||||
),
|
// ),
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Jacques",
|
// firstName: "Jacques",
|
||||||
lastName: "Gerard",
|
// lastName: "Gerard",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 3,
|
// id: 3,
|
||||||
),
|
// ),
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Jacquelines",
|
// firstName: "Jacquelines",
|
||||||
lastName: "Geraldine",
|
// lastName: "Geraldine",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 4,
|
// id: 4,
|
||||||
),
|
// ),
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Louis",
|
// firstName: "Louis",
|
||||||
lastName: "Valentin",
|
// lastName: "Valentin",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 5,
|
// id: 5,
|
||||||
),
|
// ),
|
||||||
Owner(
|
// Owner(
|
||||||
firstName: "Louise",
|
// firstName: "Louise",
|
||||||
lastName: "Valentine",
|
// lastName: "Valentine",
|
||||||
contact: "contact@gmail.com",
|
// contact: "contact@gmail.com",
|
||||||
id: 6,
|
// id: 6,
|
||||||
),
|
// ),
|
||||||
];
|
// ];
|
||||||
|
|
||||||
List<Owner>? get owners => _owners;
|
List<Owner>? get owners => _owners;
|
||||||
Owner addOwner(String firstName, String lastName, String contact) {
|
Owner addOwner(String firstName, String lastName, String contact) {
|
||||||
|
|
@ -61,7 +65,7 @@ class AddViewModel extends ChangeNotifier {
|
||||||
firstName: firstName,
|
firstName: firstName,
|
||||||
lastName: lastName,
|
lastName: lastName,
|
||||||
contact: contact,
|
contact: contact,
|
||||||
id: _owners!.last.id + 1,
|
id: _owners.last.id + 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
@ -79,4 +83,18 @@ class AddViewModel extends ChangeNotifier {
|
||||||
_askPrice = newValue;
|
_askPrice = newValue;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result<Book>> scanBook(BarcodeCapture barcode) async {
|
||||||
|
return Result.ok(
|
||||||
|
Book(
|
||||||
|
author: "Patrick K. Dewdney",
|
||||||
|
ean: barcode.barcodes.first.rawValue!,
|
||||||
|
id: 56,
|
||||||
|
priceNew: "50 EUR",
|
||||||
|
title: "Les chiens et la charrue",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result<BookInstance> sendBook() {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import 'package:seshat/domain/models/book.dart';
|
||||||
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
||||||
import 'package:seshat/ui/add_page/widgets/form_popup.dart';
|
import 'package:seshat/ui/add_page/widgets/form_popup.dart';
|
||||||
import 'package:seshat/ui/add_page/widgets/owner_popup.dart';
|
import 'package:seshat/ui/add_page/widgets/owner_popup.dart';
|
||||||
import 'package:seshat/ui/add_page/widgets/price_popup.dart';
|
import 'package:seshat/ui/add_page/widgets/confirmation_popup.dart';
|
||||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||||
|
import 'package:seshat/utils/result.dart';
|
||||||
|
|
||||||
class AddPage extends StatefulWidget {
|
class AddPage extends StatefulWidget {
|
||||||
const AddPage({super.key, required this.viewModel});
|
const AddPage({super.key, required this.viewModel});
|
||||||
|
|
@ -36,27 +38,33 @@ class _AddPageState extends State<AddPage> {
|
||||||
MobileScanner(
|
MobileScanner(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onDetect: (barcodes) async {
|
onDetect: (barcodes) async {
|
||||||
void setPrice(num newPrice) {
|
void setPrice(num newPrice) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
price = newPrice;
|
price = newPrice;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.viewModel.askPrice) {
|
Result<Book> result = await widget.viewModel.scanBook(barcodes);
|
||||||
await _priceDialogBuilder(context, setPrice, controller);
|
|
||||||
} else {
|
|
||||||
setPrice(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
switch (result) {
|
||||||
SnackBar(
|
case Ok():
|
||||||
content: Text(
|
await _confirmationDialogBuilder(
|
||||||
"Envoyé : ${barcodes.barcodes.first.rawValue} pour $price€",
|
context,
|
||||||
),
|
setPrice,
|
||||||
behavior: SnackBarBehavior.floating,
|
controller,
|
||||||
),
|
widget.viewModel,
|
||||||
);
|
result.value,
|
||||||
debugPrint(price.toString());
|
);
|
||||||
|
break;
|
||||||
|
case Error():
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text("Erreur : ${result.error}"),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
|
|
@ -128,10 +136,12 @@ class _AddPageState extends State<AddPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _priceDialogBuilder(
|
Future<void> _confirmationDialogBuilder(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Function(num) setPrice,
|
Function(num) setPrice,
|
||||||
MobileScannerController controller,
|
MobileScannerController controller,
|
||||||
|
AddViewModel viewModel,
|
||||||
|
Book book,
|
||||||
) {
|
) {
|
||||||
controller.stop();
|
controller.stop();
|
||||||
|
|
||||||
|
|
@ -143,7 +153,12 @@ Future<void> _priceDialogBuilder(
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => PricePopup(exitPopup: exitPopup, setPrice: setPrice),
|
builder: (context) => ConfirmationPopup(
|
||||||
|
exitPopup: exitPopup,
|
||||||
|
setPrice: setPrice,
|
||||||
|
viewModel: viewModel,
|
||||||
|
book: book,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
145
lib/ui/add_page/widgets/confirmation_popup.dart
Normal file
145
lib/ui/add_page/widgets/confirmation_popup.dart
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:seshat/domain/models/book.dart';
|
||||||
|
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConfirmationPopup> createState() => _ConfirmationPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConfirmationPopupState extends State<ConfirmationPopup> {
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
num price = 0;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text("Prix"),
|
||||||
|
content: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "Titre : ",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: widget.book.title),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "Auteur·ice : ",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: widget.book.author),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "Prix à neuf : ",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: widget.book.priceNew),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
(widget.viewModel.askPrice)
|
||||||
|
? TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Prix",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
suffixText: "€",
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return "Indiquez un prix";
|
||||||
|
} else if (num.tryParse(value) == null) {
|
||||||
|
return "Le prix doit être un nombre";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (newValue) {
|
||||||
|
price = num.parse(newValue!);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text("Le livre n'a pas été enregistré"),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.exitPopup(context);
|
||||||
|
},
|
||||||
|
child: Text("Annuler"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
switch (widget.viewModel.askPrice) {
|
||||||
|
case true:
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"\"${widget.book.title}\" ($price) a bien été enregistré",
|
||||||
|
),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.exitPopup(context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"\"${widget.book.title}\" (PL) a bien été enregistré",
|
||||||
|
),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.exitPopup(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text("Valider"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
(showNewOwner)
|
(showNewOwner || widget.viewModel.owners!.isEmpty)
|
||||||
? SizedBox()
|
? SizedBox()
|
||||||
: DropdownMenu<Owner>(
|
: DropdownMenu<Owner>(
|
||||||
enableFilter: true,
|
enableFilter: true,
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class PricePopup extends StatefulWidget {
|
|
||||||
const PricePopup({
|
|
||||||
super.key,
|
|
||||||
required this.exitPopup,
|
|
||||||
required this.setPrice,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Function(BuildContext) exitPopup;
|
|
||||||
final Function(num) setPrice;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<PricePopup> createState() => _PricePopupState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PricePopupState extends State<PricePopup> {
|
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
|
||||||
num? price;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text("Prix"),
|
|
||||||
content: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Prix",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
suffixText: "€",
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return "Indiquez un prix";
|
|
||||||
} else if (num.tryParse(value) == null) {
|
|
||||||
return "Le prix doit être un nombre";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onSaved: (newValue) {
|
|
||||||
price = num.parse(newValue!);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
_formKey.currentState!.save();
|
|
||||||
widget.setPrice(price!);
|
|
||||||
widget.exitPopup(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text("Valider"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in a new issue