From 166ee5b38991b7b03c579865c5dfc07bbe3aa32d Mon Sep 17 00:00:00 2001 From: alzalia1 Date: Fri, 15 Aug 2025 23:23:43 +0200 Subject: [PATCH] feat: added search --- .../book_instance_repository.dart | 9 +++ lib/data/services/api_client.dart | 31 ++++++++ lib/domain/models/search_result.dart | 16 ++++ .../sell_page/view_model/sell_view_model.dart | 68 ++++++++++++++--- .../sell_page/widgets/manual_scan_popup.dart | 46 ------------ .../widgets/manual_search_popup.dart | 75 +++++++++++++++++++ lib/ui/sell_page/widgets/scan_screen.dart | 8 +- 7 files changed, 193 insertions(+), 60 deletions(-) create mode 100644 lib/domain/models/search_result.dart delete mode 100644 lib/ui/sell_page/widgets/manual_scan_popup.dart create mode 100644 lib/ui/sell_page/widgets/manual_search_popup.dart diff --git a/lib/data/repositories/book_instance_repository.dart b/lib/data/repositories/book_instance_repository.dart index 3e5c036..4a07167 100644 --- a/lib/data/repositories/book_instance_repository.dart +++ b/lib/data/repositories/book_instance_repository.dart @@ -3,6 +3,7 @@ import 'package:seshat/domain/models/bal.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/search_result.dart'; import 'package:seshat/utils/result.dart'; class BookInstanceRepository { @@ -15,6 +16,14 @@ class BookInstanceRepository { return await _apiClient.getBookInstanceByEAN(balId, ean); } + Future>> getBySearch( + int balId, + String title, + String author, + ) async { + return await _apiClient.getBookInstanceBySearch(balId, title, author); + } + Future> sendBook( Book book, Owner owner, diff --git a/lib/data/services/api_client.dart b/lib/data/services/api_client.dart index b0e5942..d565c8f 100644 --- a/lib/data/services/api_client.dart +++ b/lib/data/services/api_client.dart @@ -8,6 +8,7 @@ import 'package:seshat/domain/models/bal.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/search_result.dart'; import 'package:seshat/utils/command.dart'; import 'package:seshat/utils/result.dart'; @@ -284,6 +285,36 @@ class ApiClient { * ============================= */ + Future>> getBookInstanceBySearch( + int balId, + String title, + String author, + ) async { + final client = Client(); + try { + final headers = await _getHeaders({"Content-Type": "application/json"}); + final body = jsonEncode({"title": title, "author": author}); + debugPrint("\n\n\n\n$body\n\n\n\n"); + final response = await client.post( + Uri.parse("https://$apiBasePath/bal/${balId.toString()}/search"), + headers: headers, + body: body, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as List; + debugPrint("\n\n\n\nJSON : $json\n\n\n\n"); + return Result.ok(json.map((el) => SearchResult.fromJSON(el)).toList()); + } else { + throw "Unknown Error"; + } + } catch (e) { + debugPrint("\n\n\n\nERROR: ${e.toString()}\n\n\n\n"); + return Result.error(Exception("API $e")); + } finally { + client.close(); + } + } + Future>> getBookInstanceByEAN( int balId, int ean, diff --git a/lib/domain/models/search_result.dart b/lib/domain/models/search_result.dart new file mode 100644 index 0000000..080fd95 --- /dev/null +++ b/lib/domain/models/search_result.dart @@ -0,0 +1,16 @@ +import 'package:seshat/domain/models/book.dart'; +import 'package:seshat/domain/models/book_instance.dart'; + +class SearchResult { + SearchResult(this.instance, this.book); + BookInstance instance; + Book book; + + factory SearchResult.fromJSON(Map json) { + BookInstance instance = BookInstance.fromJSON(json["book_instance"]); + + Book book = Book.fromJSON(json["book"]); + + return SearchResult(instance, book); + } +} diff --git a/lib/ui/sell_page/view_model/sell_view_model.dart b/lib/ui/sell_page/view_model/sell_view_model.dart index caff883..549b328 100644 --- a/lib/ui/sell_page/view_model/sell_view_model.dart +++ b/lib/ui/sell_page/view_model/sell_view_model.dart @@ -9,6 +9,7 @@ import 'package:seshat/domain/models/book.dart'; import 'package:seshat/domain/models/book_instance.dart'; import 'package:seshat/domain/models/book_stack.dart'; import 'package:seshat/domain/models/owner.dart'; +import 'package:seshat/domain/models/search_result.dart'; import 'package:seshat/utils/command.dart'; import 'package:seshat/utils/result.dart'; @@ -93,7 +94,54 @@ class SellViewModel extends ChangeNotifier { notifyListeners(); } + Future searchBook(String title, String author) async { + Bal? bal = await _balRepository.ongoingBal(); + isScanLoaded = false; + _scannedBooks.clear(); + + final result = await _bookInstanceRepository.getBySearch( + bal!.id, + title, + author, + ); + switch (result) { + case Ok(): + for (SearchResult searchResult in result.value) { + if (searchResult.instance.available == false) { + continue; + } + if (_soldBooks + .where((book) => book.instance.id == searchResult.instance.id) + .isNotEmpty) { + continue; + } + Owner owner; + final result2 = await _ownerRepository.getOwnerById( + searchResult.instance.ownerId, + ); + switch (result2) { + case Ok(): + owner = result2.value; + break; + case Error(): + continue; + } + _scannedBooks.add( + BookStack(searchResult.book, searchResult.instance, owner), + ); + } + break; + case Error(): + break; + } + + isScanLoaded = true; + notifyListeners(); + return; + } + Future scanBook(BarcodeCapture barcode) async { + isScanLoaded = false; int ean = int.parse(barcode.barcodes.first.rawValue!); Bal? bal = await _balRepository.ongoingBal(); _scannedBooks.clear(); @@ -101,6 +149,17 @@ class SellViewModel extends ChangeNotifier { final result = await _bookInstanceRepository.getByEan(bal!.id, ean); switch (result) { case Ok(): + Book book; + final result2 = await _bookRepository.getBookById( + result.value.first.bookId, + ); + switch (result2) { + case Ok(): + book = result2.value; + break; + case Error(): + return; + } for (BookInstance instance in result.value) { if (instance.available == false) { continue; @@ -110,15 +169,6 @@ class SellViewModel extends ChangeNotifier { .isNotEmpty) { continue; } - Book book; - final result2 = await _bookRepository.getBookById(instance.bookId); - switch (result2) { - case Ok(): - book = result2.value; - break; - case Error(): - continue; - } Owner owner; final result3 = await _ownerRepository.getOwnerById(instance.ownerId); switch (result3) { diff --git a/lib/ui/sell_page/widgets/manual_scan_popup.dart b/lib/ui/sell_page/widgets/manual_scan_popup.dart deleted file mode 100644 index 37d9ea7..0000000 --- a/lib/ui/sell_page/widgets/manual_scan_popup.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; -import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart'; - -class ManualScanPopup extends StatelessWidget { - const ManualScanPopup({ - super.key, - required this.viewModel, - required this.controller, - }); - - final SellViewModel viewModel; - final MobileScannerController controller; - - @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: () { - controller.start(); - Navigator.of(context).pop(); - }, - child: Text("Annuler"), - ), - ], - ); - } -} diff --git a/lib/ui/sell_page/widgets/manual_search_popup.dart b/lib/ui/sell_page/widgets/manual_search_popup.dart new file mode 100644 index 0000000..02b826d --- /dev/null +++ b/lib/ui/sell_page/widgets/manual_search_popup.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart'; +import 'package:seshat/ui/sell_page/widgets/sell_choice_popup.dart'; + +class ManualSearchPopup extends StatefulWidget { + const ManualSearchPopup({super.key, required this.viewModel}); + + final SellViewModel viewModel; + + @override + State createState() => _ManualSearchPopupState(); +} + +class _ManualSearchPopupState extends State { + String? title = ""; + String? author = ""; + + @override + Widget build(BuildContext context) { + GlobalKey _form = GlobalKey(); + + return AlertDialog( + title: Text("Recherche manuelle"), + content: Form( + key: _form, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + decoration: InputDecoration( + labelText: "Titre", + border: OutlineInputBorder(), + ), + onSaved: (newValue) => setState(() { + title = newValue; + }), + ), + SizedBox(height: 10), + TextFormField( + decoration: InputDecoration( + labelText: "Auteur", + border: OutlineInputBorder(), + ), + onSaved: (newValue) => setState(() { + author = newValue; + }), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text("Annuler"), + ), + + ElevatedButton( + onPressed: () { + _form.currentState!.save(); + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (context) => + SellChoicePopup(viewModel: widget.viewModel), + ); + widget.viewModel.searchBook(title ?? "", author ?? ""); + }, + child: Text("Rechercher"), + ), + ], + ); + } +} diff --git a/lib/ui/sell_page/widgets/scan_screen.dart b/lib/ui/sell_page/widgets/scan_screen.dart index cd86e8f..1b15ee8 100644 --- a/lib/ui/sell_page/widgets/scan_screen.dart +++ b/lib/ui/sell_page/widgets/scan_screen.dart @@ -2,7 +2,7 @@ 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'; +import 'package:seshat/ui/sell_page/widgets/manual_search_popup.dart'; import 'package:seshat/ui/sell_page/widgets/sell_choice_popup.dart'; class ScanScreen extends StatefulWidget { @@ -78,10 +78,8 @@ class _ScanScreenState extends State { showDialog( context: context, barrierDismissible: false, - builder: (context) => ManualScanPopup( - viewModel: widget.viewModel, - controller: controller, - ), + builder: (context) => + ManualSearchPopup(viewModel: widget.viewModel), ); }, child: Text("Vendre un livre sans scanner"),