fix: flow on scan + owners managment

This commit is contained in:
Alzalia 2025-08-05 15:13:52 +02:00
parent 70146055df
commit 86094b5d76
10 changed files with 283 additions and 130 deletions

3
devtools_options.yaml Normal file
View 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:

View 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;
}

View 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"],
);
}

View file

@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
class Owner extends ChangeNotifier {
class Owner {
Owner({
required this.firstName,
required this.lastName,

View file

@ -1,11 +1,7 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:seshat/config/dependencies.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() {
Logger.root.level = Level.ALL;

View file

@ -1,7 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.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/utils/result.dart';
class AddViewModel extends ChangeNotifier {
AddViewModel();
@ -15,44 +19,44 @@ class AddViewModel extends ChangeNotifier {
notifyListeners();
}
List<Owner>? _owners = [
Owner(
firstName: "Jean",
lastName: "Henri",
contact: "contact@gmail.com",
id: 1,
),
Owner(
firstName: "Jeanette",
lastName: "Henriette",
contact: "contact@gmail.com",
id: 2,
),
Owner(
firstName: "Jacques",
lastName: "Gerard",
contact: "contact@gmail.com",
id: 3,
),
Owner(
firstName: "Jacquelines",
lastName: "Geraldine",
contact: "contact@gmail.com",
id: 4,
),
Owner(
firstName: "Louis",
lastName: "Valentin",
contact: "contact@gmail.com",
id: 5,
),
Owner(
firstName: "Louise",
lastName: "Valentine",
contact: "contact@gmail.com",
id: 6,
),
];
List<Owner> _owners = [];
// Owner(
// firstName: "Jean",
// lastName: "Henri",
// contact: "contact@gmail.com",
// id: 1,
// ),
// Owner(
// firstName: "Jeanette",
// lastName: "Henriette",
// contact: "contact@gmail.com",
// id: 2,
// ),
// Owner(
// firstName: "Jacques",
// lastName: "Gerard",
// contact: "contact@gmail.com",
// id: 3,
// ),
// Owner(
// firstName: "Jacquelines",
// lastName: "Geraldine",
// contact: "contact@gmail.com",
// id: 4,
// ),
// Owner(
// firstName: "Louis",
// lastName: "Valentin",
// contact: "contact@gmail.com",
// id: 5,
// ),
// Owner(
// firstName: "Louise",
// lastName: "Valentine",
// contact: "contact@gmail.com",
// id: 6,
// ),
// ];
List<Owner>? get owners => _owners;
Owner addOwner(String firstName, String lastName, String contact) {
@ -61,7 +65,7 @@ class AddViewModel extends ChangeNotifier {
firstName: firstName,
lastName: lastName,
contact: contact,
id: _owners!.last.id + 1,
id: _owners.last.id + 1,
),
);
notifyListeners();
@ -79,4 +83,18 @@ class AddViewModel extends ChangeNotifier {
_askPrice = newValue;
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() {};
}

View file

@ -1,11 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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/widgets/form_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/utils/result.dart';
class AddPage extends StatefulWidget {
const AddPage({super.key, required this.viewModel});
@ -36,27 +38,33 @@ class _AddPageState extends State<AddPage> {
MobileScanner(
controller: controller,
onDetect: (barcodes) async {
void setPrice(num newPrice) {
void setPrice(num newPrice) async {
setState(() {
price = newPrice;
});
}
if (widget.viewModel.askPrice) {
await _priceDialogBuilder(context, setPrice, controller);
} else {
setPrice(0);
}
Result<Book> result = await widget.viewModel.scanBook(barcodes);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Envoyé : ${barcodes.barcodes.first.rawValue} pour $price",
),
behavior: SnackBarBehavior.floating,
),
);
debugPrint(price.toString());
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;
}
},
),
SafeArea(
@ -128,10 +136,12 @@ class _AddPageState extends State<AddPage> {
}
}
Future<void> _priceDialogBuilder(
Future<void> _confirmationDialogBuilder(
BuildContext context,
Function(num) setPrice,
MobileScannerController controller,
AddViewModel viewModel,
Book book,
) {
controller.stop();
@ -143,7 +153,12 @@ Future<void> _priceDialogBuilder(
return showDialog(
context: context,
barrierDismissible: false,
builder: (context) => PricePopup(exitPopup: exitPopup, setPrice: setPrice),
builder: (context) => ConfirmationPopup(
exitPopup: exitPopup,
setPrice: setPrice,
viewModel: viewModel,
book: book,
),
);
}

View 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"),
),
],
);
}
}

View file

@ -41,7 +41,7 @@ class _OwnerPopupState extends State<OwnerPopup> {
),
),
SizedBox(height: 5),
(showNewOwner)
(showNewOwner || widget.viewModel.owners!.isEmpty)
? SizedBox()
: DropdownMenu<Owner>(
enableFilter: true,

View file

@ -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"),
),
],
);
}
}