fix: overflow + owner managment error

This commit is contained in:
Alzalia 2025-08-05 15:50:37 +02:00
parent 86094b5d76
commit f8f1849d9d
4 changed files with 309 additions and 281 deletions

View file

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.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/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'; import 'package:seshat/utils/result.dart';
@ -12,6 +11,12 @@ class AddViewModel extends ChangeNotifier {
final _log = Logger("AddViewModel"); final _log = Logger("AddViewModel");
/*
* ====================
* =====[ OWNERS ]=====
* ====================
*/
Owner? _currentOwner; Owner? _currentOwner;
Owner? get currentOwner => _currentOwner; Owner? get currentOwner => _currentOwner;
set currentOwner(Owner? owner) { set currentOwner(Owner? owner) {
@ -19,55 +24,30 @@ class AddViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
List<Owner> _owners = []; final 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; List<Owner>? get owners => _owners;
Owner addOwner(String firstName, String lastName, String contact) { Owner addOwner(String firstName, String lastName, String contact) {
_owners!.add( if (_owners.isEmpty) {
Owner( _owners.add(
firstName: firstName, Owner(
lastName: lastName, firstName: firstName,
contact: contact, lastName: lastName,
id: _owners.last.id + 1, contact: contact,
), id: 1,
); ),
);
} else {
_owners.add(
Owner(
firstName: firstName,
lastName: lastName,
contact: contact,
id: _owners.last.id + 1,
),
);
}
notifyListeners(); notifyListeners();
return Owner( return Owner(
firstName: firstName, firstName: firstName,
@ -77,6 +57,12 @@ class AddViewModel extends ChangeNotifier {
); );
} }
/*
* ===================
* =====[ PRICE ]=====
* ===================
*/
bool _askPrice = true; bool _askPrice = true;
bool get askPrice => _askPrice; bool get askPrice => _askPrice;
set askPrice(bool newValue) { set askPrice(bool newValue) {
@ -84,6 +70,14 @@ class AddViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/*
* =================================
* =====[ BOOKS AND INSTANCES ]=====
* =================================
*/
/// Sends an api request with a [bacorde], then gets the [Book] that was
/// either created or retrieved. Sens the [Book] back wrapped in a [Result].
Future<Result<Book>> scanBook(BarcodeCapture barcode) async { Future<Result<Book>> scanBook(BarcodeCapture barcode) async {
return Result.ok( return Result.ok(
Book( Book(
@ -96,5 +90,8 @@ class AddViewModel extends ChangeNotifier {
); );
} }
// Result<BookInstance> sendBook() {}; /// Sens an api request with
// Result<BookInstance> newBookInstance() {
// };
} }

View file

@ -38,6 +38,18 @@ class _AddPageState extends State<AddPage> {
MobileScanner( MobileScanner(
controller: controller, controller: controller,
onDetect: (barcodes) async { onDetect: (barcodes) async {
if (widget.viewModel.currentOwner == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Attention : vous devez choisir un·e propriétaire",
),
behavior: SnackBarBehavior.floating,
),
);
return;
}
void setPrice(num newPrice) async { void setPrice(num newPrice) async {
setState(() { setState(() {
price = newPrice; price = newPrice;
@ -68,62 +80,77 @@ class _AddPageState extends State<AddPage> {
}, },
), ),
SafeArea( SafeArea(
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.center, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Center( mainAxisSize: MainAxisSize.min,
child: Card( children: [
margin: EdgeInsets.symmetric(horizontal: 50), Center(
child: Column( child: Card(
children: [ margin: EdgeInsets.symmetric(horizontal: 50),
ListenableBuilder( child: Column(
listenable: widget.viewModel, mainAxisSize: MainAxisSize.min,
builder: (context, child) => ListTile( children: [
leading: Icon(Icons.person), ListenableBuilder(
listenable: widget.viewModel,
builder: (context, child) => ListTile(
leading: Icon(Icons.person),
title: TextButton(
child: Text(
(widget.viewModel.currentOwner == null)
? "Aucun"
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
),
onPressed: () => _ownerDialogBuilder(
context,
controller,
widget.viewModel,
),
),
),
),
ListTile(
leading: Icon(Icons.attach_money),
title: TextButton( title: TextButton(
child: Text( child: Text(
(widget.viewModel.currentOwner == null) (widget.viewModel.askPrice)
? "Aucun" ? "Demander à chaque fois"
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}", : "Prix libre toujours",
),
onPressed: () => _ownerDialogBuilder(
context,
controller,
widget.viewModel,
), ),
onPressed: () {
setState(() {
widget.viewModel.askPrice =
!widget.viewModel.askPrice;
});
},
), ),
), ),
), ],
ListTile( ),
leading: Icon(Icons.attach_money),
title: TextButton(
child: Text(
(widget.viewModel.askPrice)
? "Demander à chaque fois"
: "Prix libre toujours",
),
onPressed: () {
setState(() {
widget.viewModel.askPrice =
!widget.viewModel.askPrice;
});
},
),
),
],
), ),
), ),
), SizedBox(height: 100),
Expanded(child: SizedBox()), SvgPicture.asset('assets/scan-overlay.svg'),
SvgPicture.asset('assets/scan-overlay.svg'), ],
Expanded(child: SizedBox()), ),
TextButton( ),
style: ButtonStyle( ),
backgroundColor: WidgetStatePropertyAll(theme.cardColor), SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Center(
child: TextButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(theme.cardColor),
),
onPressed: () => _formDialogBuilder(
context,
controller,
widget.viewModel,
),
child: Text("Enregistrer manuellement"),
), ),
onPressed: () =>
_formDialogBuilder(context, controller, widget.viewModel),
child: Text("Enregistrer manuellement"),
), ),
], ],
), ),

View file

@ -30,69 +30,71 @@ class _ConfirmationPopupState extends State<ConfirmationPopup> {
title: Text("Prix"), title: Text("Prix"),
content: Form( content: Form(
key: _formKey, key: _formKey,
child: Column( child: SingleChildScrollView(
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min,
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
RichText( children: [
text: TextSpan( RichText(
style: theme.textTheme.bodyMedium, text: TextSpan(
children: [ style: theme.textTheme.bodyMedium,
TextSpan( children: [
text: "Titre : ", TextSpan(
style: TextStyle(fontWeight: FontWeight.bold), 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, TextSpan(text: widget.book.title),
validator: (value) { ],
if (value == null || value.isEmpty) { ),
return "Indiquez un prix"; ),
} else if (num.tryParse(value) == null) { RichText(
return "Le prix doit être un nombre"; text: TextSpan(
} style: theme.textTheme.bodyMedium,
return null; children: [
}, TextSpan(
onSaved: (newValue) { text: "Auteur·ice : ",
price = num.parse(newValue!); style: TextStyle(fontWeight: FontWeight.bold),
}, ),
) TextSpan(text: widget.book.author),
: SizedBox(), ],
], ),
),
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: [ actions: [

View file

@ -29,133 +29,135 @@ class _OwnerPopupState extends State<OwnerPopup> {
listenable: widget.viewModel, listenable: widget.viewModel,
builder: (context, child) => AlertDialog( builder: (context, child) => AlertDialog(
title: Center(child: Text("Propriétaire du livre")), title: Center(child: Text("Propriétaire du livre")),
content: Column( content: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.center, child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center,
children: [ mainAxisSize: MainAxisSize.min,
Center( children: [
child: Text( Center(
(widget.viewModel.currentOwner == null) child: Text(
? "Choix actuel : aucun" (widget.viewModel.currentOwner == null)
: "Choix actuel : ${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}", ? "Choix actuel : aucun"
: "Choix actuel : ${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
),
), ),
), SizedBox(height: 5),
SizedBox(height: 5), (showNewOwner || widget.viewModel.owners!.isEmpty)
(showNewOwner || widget.viewModel.owners!.isEmpty) ? SizedBox()
? SizedBox() : DropdownMenu<Owner>(
: DropdownMenu<Owner>( enableFilter: true,
enableFilter: true, label: Text("Rechercher un·e propriétaire"),
label: Text("Rechercher un·e propriétaire"), dropdownMenuEntries: [
dropdownMenuEntries: [ for (var owner in widget.viewModel.owners!)
for (var owner in widget.viewModel.owners!) DropdownMenuEntry(
DropdownMenuEntry( value: owner,
value: owner, label: "${owner.firstName} ${owner.lastName}",
label: "${owner.firstName} ${owner.lastName}", style: ButtonStyle(
style: ButtonStyle( backgroundColor:
backgroundColor: (widget.viewModel.currentOwner == owner)
(widget.viewModel.currentOwner == owner) ? WidgetStatePropertyAll<Color>(
? WidgetStatePropertyAll<Color>( theme.highlightColor,
theme.highlightColor, )
) : WidgetStatePropertyAll<Color>(
: WidgetStatePropertyAll<Color>( theme.canvasColor,
theme.canvasColor, ),
), ),
), ),
),
],
initialSelection: widget.viewModel.currentOwner,
onSelected: (Owner? owner) {
widget.viewModel.currentOwner = owner;
},
),
SizedBox(height: 20),
TextButton(
onPressed: () {
setState(() {
showNewOwner = !showNewOwner;
});
},
child: Text(
(showNewOwner) ? "Annuler" : "Ajouter un propriétaire",
),
),
(!showNewOwner)
? SizedBox()
: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Nom",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
lastName = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un nom";
}
return null;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
labelText: "Prénom",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
firstName = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un prénom";
}
return null;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
labelText: "Contact",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
contact = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un moyen de contact";
}
return null;
},
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
widget.viewModel.currentOwner = widget.viewModel
.addOwner(firstName!, lastName!, contact!);
setState(() {
showNewOwner = false;
});
}
},
child: Text("Créer"),
),
], ],
initialSelection: widget.viewModel.currentOwner,
onSelected: (Owner? owner) {
widget.viewModel.currentOwner = owner;
},
), ),
), SizedBox(height: 20),
], TextButton(
onPressed: () {
setState(() {
showNewOwner = !showNewOwner;
});
},
child: Text(
(showNewOwner) ? "Annuler" : "Ajouter un propriétaire",
),
),
(!showNewOwner)
? SizedBox()
: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Nom",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
lastName = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un nom";
}
return null;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
labelText: "Prénom",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
firstName = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un prénom";
}
return null;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
labelText: "Contact",
border: OutlineInputBorder(),
),
onSaved: (newValue) {
setState(() {
contact = newValue;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return "Indiquez un moyen de contact";
}
return null;
},
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
widget.viewModel.currentOwner = widget.viewModel
.addOwner(firstName!, lastName!, contact!);
setState(() {
showNewOwner = false;
});
}
},
child: Text("Créer"),
),
],
),
),
],
),
), ),
actions: [ actions: [