feat: add an owner + sell screen
This commit is contained in:
parent
d2cbb43bcb
commit
073f8bd334
15 changed files with 354 additions and 82 deletions
|
|
@ -32,15 +32,14 @@ class AddViewModel extends ChangeNotifier {
|
|||
}
|
||||
|
||||
List<Owner> _owners = [];
|
||||
|
||||
List<Owner>? get owners => _owners;
|
||||
|
||||
Future<Result<Owner>> addOwner(
|
||||
String firstName,
|
||||
String lastName,
|
||||
String contact,
|
||||
) async {
|
||||
final result = await _ownerRepository.postOwner(
|
||||
debugPrint("\n\n\n\n(2) TRANFERRING\n\n\n\n");
|
||||
final result = await _ownerRepository.addOwner(
|
||||
firstName,
|
||||
lastName,
|
||||
contact,
|
||||
|
|
@ -52,7 +51,9 @@ class AddViewModel extends ChangeNotifier {
|
|||
|
||||
switch (secondResult) {
|
||||
case Ok():
|
||||
debugPrint("\n\n\n${secondResult.value.length}");
|
||||
_owners = secondResult.value;
|
||||
debugPrint("\n\n\n${_owners.length}");
|
||||
_currentOwner = result.value;
|
||||
notifyListeners();
|
||||
return Result.ok(result.value);
|
||||
|
|
@ -130,16 +131,7 @@ class AddViewModel extends ChangeNotifier {
|
|||
debugPrint("Oupsie daysie, ${result.error}");
|
||||
}
|
||||
notifyListeners();
|
||||
sub = _ownerRepository.liveOwners.listen((Owner owner) {
|
||||
debugPrint("\n\n\n\n[5] Updated UI : $owner\n\n\n\n");
|
||||
_owners.add(owner);
|
||||
_owners.sort(
|
||||
(a, b) => "${a.firstName} ${a.lastName}".compareTo(
|
||||
"${b.firstName} ${b.lastName}",
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ class _AddPageState extends State<AddPage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Center(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 50),
|
||||
|
|
@ -132,12 +133,11 @@ class _AddPageState extends State<AddPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 100),
|
||||
SvgPicture.asset('assets/scan-overlay.svg'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(child: SvgPicture.asset('assets/scan-overlay.svg')),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
|
|
@ -157,6 +157,7 @@ class _AddPageState extends State<AddPage> {
|
|||
child: Text("Enregistrer manuellement"),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class OwnerPopup extends StatefulWidget {
|
|||
|
||||
class _OwnerPopupState extends State<OwnerPopup> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
bool showNewOwner = false;
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
|
|
@ -25,6 +26,9 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
searchController.text = (widget.viewModel.currentOwner == null)
|
||||
? ""
|
||||
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}";
|
||||
final theme = Theme.of(context);
|
||||
return ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
|
|
@ -35,19 +39,14 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
(widget.viewModel.currentOwner == null)
|
||||
? "Choix actuel : aucun"
|
||||
: "Choix actuel : ${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
(showNewOwner || widget.viewModel.owners!.isEmpty)
|
||||
? SizedBox()
|
||||
: DropdownMenu<Owner>(
|
||||
enableFilter: true,
|
||||
controller: searchController,
|
||||
label: Text("Rechercher un·e propriétaire"),
|
||||
requestFocusOnTap: true,
|
||||
dropdownMenuEntries: [
|
||||
for (var owner in widget.viewModel.owners!)
|
||||
DropdownMenuEntry(
|
||||
|
|
@ -65,7 +64,6 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
|||
),
|
||||
),
|
||||
],
|
||||
initialSelection: widget.viewModel.currentOwner,
|
||||
onSelected: (Owner? owner) {
|
||||
widget.viewModel.currentOwner = owner;
|
||||
},
|
||||
|
|
@ -151,6 +149,7 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
|||
if (showNewOwner) {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_formKey.currentState!.save();
|
||||
debugPrint("\n\n\n\n(1) SENDING REQUEST\n\n\n\n");
|
||||
await widget.viewModel.addOwner(
|
||||
firstName!,
|
||||
lastName!,
|
||||
|
|
@ -160,9 +159,8 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
|||
showNewOwner = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
widget.onPressAccept(context);
|
||||
}
|
||||
widget.onPressAccept(context);
|
||||
},
|
||||
child: Text("Valider"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
|
||||
class SellPage extends StatelessWidget {
|
||||
const SellPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: AppNavigationBar(startIndex: 2),
|
||||
body: Center(child: Text("Sell page.")),
|
||||
);
|
||||
// return Center(child: Text("Sell page."));
|
||||
}
|
||||
}
|
||||
39
lib/ui/sell_page/view_model/sell_view_model.dart
Normal file
39
lib/ui/sell_page/view_model/sell_view_model.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:seshat/domain/models/book_instance.dart';
|
||||
|
||||
class SellViewModel extends ChangeNotifier {
|
||||
SellViewModel();
|
||||
|
||||
bool _showScan = false;
|
||||
bool get showScan => _showScan;
|
||||
set showScan(bool newValue) {
|
||||
_showScan = newValue;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
final List<BookInstance> _scannedBooks = [];
|
||||
get scannedBooks => _scannedBooks;
|
||||
void scanBook(BarcodeCapture barcode) {
|
||||
final addedBook = BookInstance(
|
||||
balId: 5,
|
||||
bookId: 5,
|
||||
id: _scannedBooks.length,
|
||||
ownerId: 5,
|
||||
price: 5,
|
||||
status: true,
|
||||
);
|
||||
_scannedBooks.add(addedBook);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void sendSell() {
|
||||
_scannedBooks.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void deleteBook(int id) {
|
||||
_scannedBooks.removeWhere((book) => book.id == id);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
39
lib/ui/sell_page/widgets/manual_scan_popup.dart
Normal file
39
lib/ui/sell_page/widgets/manual_scan_popup.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||
|
||||
class ManualScanPopup extends StatelessWidget {
|
||||
ManualScanPopup({required this.viewModel});
|
||||
|
||||
final SellViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Recherche manuelle"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Rechercher",
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.arrow_forward),
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 200),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text("Annuler"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
73
lib/ui/sell_page/widgets/scan_screen.dart
Normal file
73
lib/ui/sell_page/widgets/scan_screen.dart
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||
import 'package:seshat/ui/sell_page/widgets/manual_scan_popup.dart';
|
||||
|
||||
class ScanScreen extends StatefulWidget {
|
||||
ScanScreen({super.key, required this.viewModel});
|
||||
|
||||
SellViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<ScanScreen> createState() => _ScanScreenState();
|
||||
}
|
||||
|
||||
class _ScanScreenState extends State<ScanScreen> {
|
||||
final MobileScannerController controller = MobileScannerController(
|
||||
formats: [BarcodeFormat.ean13],
|
||||
detectionTimeoutMs: 1000,
|
||||
);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: controller,
|
||||
onDetect: (barcodes) async {
|
||||
widget.viewModel.showScan = false;
|
||||
widget.viewModel.scanBook(barcodes);
|
||||
controller.dispose();
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
widget.viewModel.showScan = false;
|
||||
},
|
||||
icon: Icon(Icons.arrow_back),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(child: SvgPicture.asset('assets/scan-overlay.svg')),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(theme.cardColor),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) =>
|
||||
ManualScanPopup(viewModel: widget.viewModel),
|
||||
);
|
||||
},
|
||||
child: Text("Vendre un livre sans scanner"),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
116
lib/ui/sell_page/widgets/sell_page.dart
Normal file
116
lib/ui/sell_page/widgets/sell_page.dart
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:seshat/domain/models/book_instance.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||
import 'package:seshat/ui/sell_page/widgets/scan_screen.dart';
|
||||
|
||||
class SellPage extends StatefulWidget {
|
||||
const SellPage({super.key, required this.viewModel});
|
||||
|
||||
final SellViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<SellPage> createState() => _SellPageState();
|
||||
}
|
||||
|
||||
class _SellPageState extends State<SellPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: AppNavigationBar(startIndex: 2),
|
||||
body: ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 6),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
for (BookInstance bookInstance
|
||||
in widget.viewModel.scannedBooks)
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Text(
|
||||
"${bookInstance.price.toString()}€",
|
||||
style: TextStyle(fontSize: 30),
|
||||
),
|
||||
title: Text(
|
||||
"Les chiens et la charrue · Patrick K. Dewdney ${bookInstance.id}",
|
||||
),
|
||||
subtitle: Text("Union Étudiante Auvergne"),
|
||||
trailing: IconButton(
|
||||
onPressed: () {
|
||||
widget.viewModel.deleteBook(
|
||||
bookInstance.id,
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.delete),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
Text("Somme minimum requise : 20€"),
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Argent reçu",
|
||||
helperText:
|
||||
"L'argent reçu sera réparti automatiquement",
|
||||
suffixText: "€",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
widget.viewModel.sendSell();
|
||||
},
|
||||
icon: Icon(Icons.check),
|
||||
style: ButtonStyle(
|
||||
iconSize: WidgetStatePropertyAll(70),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 70),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
widget.viewModel.showScan = true;
|
||||
},
|
||||
icon: Icon(Icons.add),
|
||||
style: ButtonStyle(
|
||||
iconSize: WidgetStatePropertyAll(70),
|
||||
elevation: WidgetStatePropertyAll(50),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
(widget.viewModel.showScan)
|
||||
? ScanScreen(viewModel: widget.viewModel)
|
||||
: SizedBox(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
// return Center(child: Text("Sell page."));
|
||||
}
|
||||
}
|
||||
Reference in a new issue