fix: continuing error managment and documentation
This commit is contained in:
		
							parent
							
								
									59e1c2558c
								
							
						
					
					
						commit
						dad000a1b9
					
				
					 24 changed files with 389 additions and 182 deletions
				
			
		|  | @ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; | |||
| import 'package:seshat/data/services/auth_client.dart'; | ||||
| import 'package:seshat/utils/result.dart'; | ||||
| 
 | ||||
| /// Repository to manage authentification | ||||
| class AuthRepository extends ChangeNotifier { | ||||
|   AuthRepository({required AuthClient authClient}) : _authClient = authClient; | ||||
| 
 | ||||
|  | @ -9,6 +10,7 @@ class AuthRepository extends ChangeNotifier { | |||
| 
 | ||||
|   bool? _isAuthenticated; | ||||
| 
 | ||||
|   /// Checks the validity of the token if not already checked | ||||
|   Future<bool> get isLoggedIn async { | ||||
|     if (_isAuthenticated != null) { | ||||
|       return _isAuthenticated!; | ||||
|  | @ -25,6 +27,7 @@ class AuthRepository extends ChangeNotifier { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Logs in the user | ||||
|   Future<Result<void>> login(String username, String password) async { | ||||
|     try { | ||||
|       final result = await _authClient.login(username, password); | ||||
|  | @ -33,13 +36,14 @@ class AuthRepository extends ChangeNotifier { | |||
|           _isAuthenticated = true; | ||||
|           return Result.ok(()); | ||||
|         case Error(): | ||||
|           return Result.error(result.error); | ||||
|           return result; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       return Result.error(Exception(e)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the API's remote version | ||||
|   Future<Result<int>> getRemoteApiVersion() async { | ||||
|     return await _authClient.getRemoteApiVersion(); | ||||
|   } | ||||
|  |  | |||
|  | @ -5,13 +5,19 @@ import 'package:seshat/domain/models/bal_stats.dart'; | |||
| import 'package:seshat/domain/models/enums.dart'; | ||||
| import 'package:seshat/utils/result.dart'; | ||||
| 
 | ||||
| /// Repository to manage [Bal] | ||||
| class BalRepository { | ||||
|   BalRepository({required ApiClient apiClient}) : _apiClient = apiClient; | ||||
| 
 | ||||
|   final ApiClient _apiClient; | ||||
|   List<Bal>? _bals; | ||||
|   Accounting? accounting; | ||||
| 
 | ||||
|   /// [List<Bal>] of all the user's [Bal] | ||||
|   List<Bal>? _bals; | ||||
| 
 | ||||
|   /// [Accounting] of [Bal], mapped by [Bal] id | ||||
|   final Map<int, Accounting?> _accountingMap = {}; | ||||
| 
 | ||||
|   /// Gets a list of all [Bal] from cache or remote | ||||
|   Future<Result<List<Bal>>> getBals() async { | ||||
|     if (_bals != null) { | ||||
|       return Result.ok(_bals!); | ||||
|  | @ -26,6 +32,7 @@ class BalRepository { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets a list of all [Bal] from remote only | ||||
|   Future<Result<List<Bal>>> _getBalsNoCache() async { | ||||
|     final result = await _apiClient.getBals(); | ||||
|     switch (result) { | ||||
|  | @ -37,15 +44,16 @@ class BalRepository { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal>> balById(int id) async { | ||||
|   /// Gets a [Bal] by [balId], either from cache or remote | ||||
|   Future<Result<Bal>> balById(int balId) async { | ||||
|     if (_bals == null) { | ||||
|       await getBals(); | ||||
|     } | ||||
|     Bal? bal = _bals!.where((bal) => bal.id == id).firstOrNull; | ||||
|     Bal? bal = _bals!.where((bal) => bal.id == balId).firstOrNull; | ||||
|     if (bal != null) { | ||||
|       return Result.ok(bal); | ||||
|     } | ||||
|     final result = await _apiClient.getBalById(id); | ||||
|     final result = await _apiClient.getBalById(balId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         return Result.ok(result.value); | ||||
|  | @ -54,11 +62,13 @@ class BalRepository { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Return wether or not a [Bal] is currently [BalState.ongoing] | ||||
|   bool isABalOngoing() { | ||||
|     return _bals?.where((bal) => bal.state == BalState.ongoing).isNotEmpty ?? | ||||
|         false; | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the [Bal] that is [BalState.ongoing] | ||||
|   Future<Bal?> ongoingBal() async { | ||||
|     if (_bals == null) { | ||||
|       await _getBalsNoCache(); | ||||
|  | @ -66,12 +76,14 @@ class BalRepository { | |||
|     return _bals!.where((bal) => bal.state == BalState.ongoing).firstOrNull; | ||||
|   } | ||||
| 
 | ||||
|   /// Stops a [Bal] and refresh cache | ||||
|   Future<Result<Bal>> stopBal(int id) async { | ||||
|     final result = await _apiClient.stopBal(id); | ||||
|     _getBalsNoCache(); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Starts a [Bal] and refresh cache | ||||
|   Future<Result<Bal>> startBal(int id) async { | ||||
|     if (isABalOngoing()) { | ||||
|       return Result.error( | ||||
|  | @ -83,52 +95,62 @@ class BalRepository { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Changes a [Bal]'s [name], [startTime] or [endTime] | ||||
|   Future<Result<Bal>> editBal( | ||||
|     int id, | ||||
|     String name, | ||||
|     DateTime start, | ||||
|     DateTime end, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final result = await _apiClient.editBal(id, name, start, end); | ||||
|     final result = await _apiClient.editBal(id, name, startTime, endTime); | ||||
|     await _getBalsNoCache(); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<void>> addBal(String name, DateTime start, DateTime end) async { | ||||
|     final result = await _apiClient.addBal(name, start, end); | ||||
|   /// Creates a [Bal] from its [name], [startTime] and [endTime] | ||||
|   Future<Result<void>> addBal( | ||||
|     String name, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final result = await _apiClient.addBal(name, startTime, endTime); | ||||
|     await _getBalsNoCache(); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<BalStats>> getBalStats(int id) async { | ||||
|     return _apiClient.getBalStats(id); | ||||
|   /// Gets a [BalStats] from its [balId] | ||||
|   Future<Result<BalStats>> getBalStats(int balId) async { | ||||
|     return _apiClient.getBalStats(balId); | ||||
|   } | ||||
| 
 | ||||
|   /// Get [Accounting] of a [Bal] from remote only | ||||
|   Future<Result<Accounting>> getAccountingNoCache(int balId) async { | ||||
|     final result = await _apiClient.getAccounting(balId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         accounting = result.value; | ||||
|         _accountingMap[balId] = result.value; | ||||
|         break; | ||||
|       default: | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Get [Accounting] of a [Bal] from cache or remote | ||||
|   Future<Result<Accounting>> getAccounting(int balId) async { | ||||
|     if (accounting != null) { | ||||
|       return Result.ok(accounting!); | ||||
|     if (_accountingMap[balId] != null) { | ||||
|       return Result.ok(_accountingMap[balId]!); | ||||
|     } | ||||
|     final result = await _apiClient.getAccounting(balId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         accounting = result.value; | ||||
|         _accountingMap[balId] = result.value; | ||||
|         break; | ||||
|       default: | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Manages what returning (of type [ReturnType]) does to cache and notifies remote | ||||
|   Future<Result<void>> returnToId( | ||||
|     int balId, | ||||
|     int ownerId, | ||||
|  | @ -139,26 +161,32 @@ class BalRepository { | |||
|       case Ok(): | ||||
|         switch (type) { | ||||
|           case ReturnType.books: | ||||
|             final owner = accounting?.owners | ||||
|             final owner = _accountingMap[balId]?.owners | ||||
|                 .where((el) => el.ownerId == ownerId) | ||||
|                 .firstOrNull; | ||||
|             if (owner?.owedMoney == 0) { | ||||
|               accounting?.owners.removeWhere((el) => el.ownerId == ownerId); | ||||
|               _accountingMap[balId]?.owners.removeWhere( | ||||
|                 (el) => el.ownerId == ownerId, | ||||
|               ); | ||||
|             } | ||||
|             owner?.owed = []; | ||||
|             owner?.owedInstances = []; | ||||
|             break; | ||||
|           case ReturnType.money: | ||||
|             final owner = accounting?.owners | ||||
|             final owner = _accountingMap[balId]?.owners | ||||
|                 .where((el) => el.ownerId == ownerId) | ||||
|                 .firstOrNull; | ||||
|             if (owner?.owed == null || owner!.owed.isEmpty) { | ||||
|               accounting?.owners.removeWhere((el) => el.ownerId == ownerId); | ||||
|               _accountingMap[balId]?.owners.removeWhere( | ||||
|                 (el) => el.ownerId == ownerId, | ||||
|               ); | ||||
|             } | ||||
|             owner?.owedMoney = 0; | ||||
|             break; | ||||
|           case ReturnType.all: | ||||
|             accounting?.owners.removeWhere((el) => el.ownerId == ownerId); | ||||
|             _accountingMap[balId]?.owners.removeWhere( | ||||
|               (el) => el.ownerId == ownerId, | ||||
|             ); | ||||
|             break; | ||||
|         } | ||||
|         break; | ||||
|  |  | |||
|  | @ -6,16 +6,19 @@ import 'package:seshat/domain/models/owner.dart'; | |||
| import 'package:seshat/domain/models/search_result.dart'; | ||||
| import 'package:seshat/utils/result.dart'; | ||||
| 
 | ||||
| /// Repository to manage [BookInstance] | ||||
| class BookInstanceRepository { | ||||
|   BookInstanceRepository({required ApiClient apiClient}) | ||||
|     : _apiClient = apiClient; | ||||
| 
 | ||||
|   final ApiClient _apiClient; | ||||
| 
 | ||||
|   /// Gets a [List<BookInstance>] from an [ean] | ||||
|   Future<Result<List<BookInstance>>> getByEan(int balId, int ean) async { | ||||
|     return await _apiClient.getBookInstanceByEAN(balId, ean); | ||||
|   } | ||||
| 
 | ||||
|   /// Gets a [List<BookInstance>] from a [title] and [author] | ||||
|   Future<Result<List<SearchResult>>> getBySearch( | ||||
|     int balId, | ||||
|     String title, | ||||
|  | @ -24,15 +27,17 @@ class BookInstanceRepository { | |||
|     return await _apiClient.getBookInstanceBySearch(balId, title, author); | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<BookInstance>> sendBook( | ||||
|   /// Sends a new [BookInstance]'s [book], [owner], [bal] and [price] | ||||
|   Future<Result<BookInstance>> sendNewBookInstance( | ||||
|     Book book, | ||||
|     Owner owner, | ||||
|     Bal bal, | ||||
|     double price, | ||||
|   ) async { | ||||
|     return await _apiClient.sendBook(book, owner, bal, price); | ||||
|     return await _apiClient.sendNewBookInstance(book, owner, bal, price); | ||||
|   } | ||||
| 
 | ||||
|   /// Sells a [List<BookInstance>] | ||||
|   Future<Result<void>> sellBooks(List<BookInstance> books) async { | ||||
|     Map<String, double?> res = {}; | ||||
|     for (BookInstance instance in books) { | ||||
|  |  | |||
|  | @ -2,16 +2,19 @@ import 'package:seshat/data/services/api_client.dart'; | |||
| import 'package:seshat/domain/models/book.dart'; | ||||
| import 'package:seshat/utils/result.dart'; | ||||
| 
 | ||||
| /// Repository to manage [Book] | ||||
| class BookRepository { | ||||
|   BookRepository({required ApiClient apiClient}) : _apiClient = apiClient; | ||||
| 
 | ||||
|   final ApiClient _apiClient; | ||||
| 
 | ||||
|   /// Gets a [Book] by its [ean] | ||||
|   Future<Result<Book>> getBookByEAN(String ean) async { | ||||
|     return await _apiClient.getBookByEAN(ean); | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Book>> getBookById(int id) async { | ||||
|     return await _apiClient.getBookById(id); | ||||
|   /// Gets a [Book] by its [bookId] | ||||
|   Future<Result<Book>> getBookById(int bookId) async { | ||||
|     return await _apiClient.getBookById(bookId); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import 'package:seshat/data/services/websocket_client.dart'; | |||
| import 'package:seshat/domain/models/owner.dart'; | ||||
| import 'package:seshat/utils/result.dart'; | ||||
| 
 | ||||
| /// Repository to manage [Owner] | ||||
| class OwnerRepository { | ||||
|   OwnerRepository({ | ||||
|     required ApiClient apiClient, | ||||
|  | @ -14,18 +15,25 @@ class OwnerRepository { | |||
| 
 | ||||
|   final ApiClient _apiClient; | ||||
|   final WebsocketClient _wsClient; | ||||
|   late final StreamSubscription sub; | ||||
|   List<Owner>? _cachedOwners; | ||||
|   Owner? _sectionOwner; | ||||
| 
 | ||||
|   Future<Result<Owner>> get sectionOwner async { | ||||
|     if (_sectionOwner != null) { | ||||
|       return Result.ok(_sectionOwner!); | ||||
|   /// [StreamSubscription] to the [Stream<Owner>] for [_wsClient] | ||||
|   late final StreamSubscription sub; | ||||
| 
 | ||||
|   /// [List<Owner>] of owners, updated by [_wsClient] | ||||
|   List<Owner>? _cachedOwners; | ||||
| 
 | ||||
|   /// [Owner] of the current user | ||||
|   Owner? _ownerOfUser; | ||||
| 
 | ||||
|   /// [Owner] of the current user | ||||
|   Future<Result<Owner>> get ownerOfUser async { | ||||
|     if (_ownerOfUser != null) { | ||||
|       return Result.ok(_ownerOfUser!); | ||||
|     } | ||||
|     final result = await _apiClient.getSectionOwner(); | ||||
|     final result = await _apiClient.getOwnerOfUser(); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _sectionOwner = result.value; | ||||
|         _ownerOfUser = result.value; | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|  | @ -33,16 +41,17 @@ class OwnerRepository { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Owner>> getOwnerById(int id) async { | ||||
|   /// Gets an [Owner] from its [ownerId] | ||||
|   Future<Result<Owner>> getOwnerById(int ownerId) async { | ||||
|     if (_cachedOwners != null) { | ||||
|       final result1 = _cachedOwners! | ||||
|           .where((owner) => owner.id == id) | ||||
|           .where((owner) => owner.id == ownerId) | ||||
|           .firstOrNull; | ||||
|       if (result1 != null) { | ||||
|         return Result.ok(result1); | ||||
|       } | ||||
|     } | ||||
|     return await _apiClient.getOwnerById(id); | ||||
|     return await _apiClient.getOwnerById(ownerId); | ||||
|   } | ||||
| 
 | ||||
|   /// Adds an [Owner] to the database, and gets the resulting [Owner]. | ||||
|  |  | |||
|  | @ -9,9 +9,9 @@ import 'package:seshat/domain/models/bal.dart'; | |||
| import 'package:seshat/domain/models/bal_stats.dart'; | ||||
| import 'package:seshat/domain/models/book.dart'; | ||||
| import 'package:seshat/domain/models/book_instance.dart'; | ||||
| import 'package:seshat/domain/models/enums.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'; | ||||
| 
 | ||||
| extension StringExtension on String { | ||||
|  | @ -20,12 +20,17 @@ extension StringExtension on String { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /// API Client to manage all authenticated REST routes | ||||
| class ApiClient { | ||||
|   ApiClient({String? host, int? port}); | ||||
|   ApiClient(); | ||||
| 
 | ||||
|   late final Command0 load; | ||||
|   /// JWT for registration | ||||
|   String? token; | ||||
| 
 | ||||
|   /// Readiness of the API Client | ||||
|   bool isReady = false; | ||||
| 
 | ||||
|   /// Storage to access JWT | ||||
|   FlutterSecureStorage? _secureStorage; | ||||
|   Logger log = Logger( | ||||
|     printer: PrettyPrinter( | ||||
|  | @ -36,10 +41,12 @@ class ApiClient { | |||
|     ), | ||||
|   ); | ||||
| 
 | ||||
|   /// Initializes connection to the [_secureStorage] | ||||
|   Future<void> _initStore() async { | ||||
|     _secureStorage ??= const FlutterSecureStorage(); | ||||
|   } | ||||
| 
 | ||||
|   /// Generates authorization headers and option [additionalHeaders] | ||||
|   Future<Map<String, String>> _getHeaders([ | ||||
|     Map<String, String>? additionalHeaders, | ||||
|   ]) async { | ||||
|  | @ -55,6 +62,7 @@ class ApiClient { | |||
|  * ======================== | ||||
| */ | ||||
| 
 | ||||
|   /// Gets data about a BAL's needed returns | ||||
|   Future<Result<Accounting>> getAccounting(int balId) async { | ||||
|     final url = "https://$apiBasePath/bal/$balId/accounting"; | ||||
|     log.i("Fetching: getAccounting ($url)"); | ||||
|  | @ -81,6 +89,8 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Notifies the server that either Books, Money or All has been returned to the user | ||||
|   /// [type] is the stringified version of [ReturnType] | ||||
|   Future<Result<void>> returnToId(int balId, int ownerId, String type) async { | ||||
|     final url = | ||||
|         "https://$apiBasePath/bal/${balId.toString()}/accounting/return/${ownerId.toString()}"; | ||||
|  | @ -120,8 +130,9 @@ class ApiClient { | |||
|  * ================= | ||||
| */ | ||||
| 
 | ||||
|   Future<Result<BalStats>> getBalStats(int id) async { | ||||
|     final url = "https://$apiBasePath/bal/${id.toString()}/stats"; | ||||
|   /// Get stats about a BAL's performance | ||||
|   Future<Result<BalStats>> getBalStats(int balId) async { | ||||
|     final url = "https://$apiBasePath/bal/${balId.toString()}/stats"; | ||||
|     log.i("Fetching: getBalStats ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -148,8 +159,9 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal>> stopBal(int id) async { | ||||
|     final url = "https://$apiBasePath/bal/${id.toString()}/stop"; | ||||
|   /// Stops a BAL, putting it's [BalState] to [BalState.ended] | ||||
|   Future<Result<Bal>> stopBal(int balId) async { | ||||
|     final url = "https://$apiBasePath/bal/${balId.toString()}/stop"; | ||||
|     log.i("Fetching: stopBal ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -176,8 +188,9 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal>> startBal(int id) async { | ||||
|     final url = "https://$apiBasePath/bal/${id.toString()}/start"; | ||||
|   /// Starts a BAL, putting it's [BalState] to [BalState.ongoing] | ||||
|   Future<Result<Bal>> startBal(int balId) async { | ||||
|     final url = "https://$apiBasePath/bal/${balId.toString()}/start"; | ||||
|     log.i("Fetching: startBal ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -204,21 +217,22 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Changes the information about a [Bal], such as its [name], [startTime] or [endTime] | ||||
|   Future<Result<Bal>> editBal( | ||||
|     int id, | ||||
|     int balId, | ||||
|     String name, | ||||
|     DateTime start, | ||||
|     DateTime end, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final url = "https://$apiBasePath/bal/${id.toString()}"; | ||||
|     final url = "https://$apiBasePath/bal/${balId.toString()}"; | ||||
|     log.i("Fetching: editBal ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|       final headers = await _getHeaders({"Content-Type": "application/json"}); | ||||
|       final body = { | ||||
|         "name": name, | ||||
|         "start_timestamp": (start.millisecondsSinceEpoch / 1000).round(), | ||||
|         "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), | ||||
|         "start_timestamp": (startTime.millisecondsSinceEpoch / 1000).round(), | ||||
|         "end_timestamp": (endTime.millisecondsSinceEpoch / 1000).round(), | ||||
|       }; | ||||
|       final response = await client.patch( | ||||
|         Uri.parse(url), | ||||
|  | @ -244,8 +258,9 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal>> getBalById(int id) async { | ||||
|     final url = "https://$apiBasePath/bal/${id.toString()}"; | ||||
|   /// Gets a [Bal] from it's [balId] | ||||
|   Future<Result<Bal>> getBalById(int balId) async { | ||||
|     final url = "https://$apiBasePath/bal/${balId.toString()}"; | ||||
|     log.i("Fetching: getBalById ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -270,7 +285,12 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal>> addBal(String name, DateTime start, DateTime end) async { | ||||
|   /// Adds a [Bal] from it's [name], [startTime] and [endTime] | ||||
|   Future<Result<Bal>> addBal( | ||||
|     String name, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final url = "https://$apiBasePath/bal"; | ||||
|     log.i("Fetching: addBal ($url)"); | ||||
|     final client = Client(); | ||||
|  | @ -278,8 +298,8 @@ class ApiClient { | |||
|       final headers = await _getHeaders({"Content-Type": "application/json"}); | ||||
|       final body = { | ||||
|         "name": name, | ||||
|         "start_timestamp": (start.millisecondsSinceEpoch / 1000).round(), | ||||
|         "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), | ||||
|         "start_timestamp": (startTime.millisecondsSinceEpoch / 1000).round(), | ||||
|         "end_timestamp": (endTime.millisecondsSinceEpoch / 1000).round(), | ||||
|       }; | ||||
|       final response = await client.post( | ||||
|         Uri.parse(url), | ||||
|  | @ -303,6 +323,7 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets a [List<Bal>] of all [Bal] | ||||
|   Future<Result<List<Bal>>> getBals() async { | ||||
|     final url = "https://$apiBasePath/bals"; | ||||
|     log.i("Fetching: getBals ($url)"); | ||||
|  | @ -328,7 +349,8 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Bal?>> getCurrentBal() async { | ||||
|   /// Gets the ongoing BAL for the user | ||||
|   Future<Result<Bal?>> getOngoingBal() async { | ||||
|     final url = "https://$apiBasePath/bal/current"; | ||||
|     log.i("Fetching: getCurrentBal ($url)"); | ||||
|     final client = Client(); | ||||
|  | @ -356,8 +378,9 @@ class ApiClient { | |||
|  * =================== | ||||
| */ | ||||
| 
 | ||||
|   Future<Result<Book>> getBookById(int id) async { | ||||
|     final url = "https://$apiBasePath/book/id/${id.toString()}"; | ||||
|   /// Gets a [Book] by its [bookId] | ||||
|   Future<Result<Book>> getBookById(int bookId) async { | ||||
|     final url = "https://$apiBasePath/book/id/${bookId.toString()}"; | ||||
|     log.i("Fetching: getBookById ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -380,6 +403,7 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets a [Book] from its [ean] | ||||
|   Future<Result<Book>> getBookByEAN(String ean) async { | ||||
|     final url = "https://$apiBasePath/book/ean/$ean"; | ||||
|     log.i("Fetching: getBookByEan ($url)"); | ||||
|  | @ -410,6 +434,7 @@ class ApiClient { | |||
|  * ============================= | ||||
| */ | ||||
| 
 | ||||
|   /// Gets a [BookInstance] from it's [title], [author] or both | ||||
|   Future<Result<List<SearchResult>>> getBookInstanceBySearch( | ||||
|     int balId, | ||||
|     String title, | ||||
|  | @ -445,6 +470,7 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets a [BookInstance] from it's [ean] | ||||
|   Future<Result<List<BookInstance>>> getBookInstanceByEAN( | ||||
|     int balId, | ||||
|     int ean, | ||||
|  | @ -475,6 +501,10 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Notifies the server of the sell of multiple [BookInstance]. [books] is in the form of | ||||
|   /// ```dart | ||||
|   /// final books = {"aBookInstanceId": 6.0} // and its price | ||||
|   /// ``` | ||||
|   Future<Result<void>> sellBooks(Map<String, double?> books) async { | ||||
|     final url = "https://$apiBasePath/book_instance/sell/bulk"; | ||||
|     log.i("Fetching: sellBooks ($url)"); | ||||
|  | @ -507,7 +537,8 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<BookInstance>> sendBook( | ||||
|   /// Creates a new [BookInstance] from it's [book], it's [owner], it's [bal] and it's [price] | ||||
|   Future<Result<BookInstance>> sendNewBookInstance( | ||||
|     Book book, | ||||
|     Owner owner, | ||||
|     Bal bal, | ||||
|  | @ -551,8 +582,9 @@ class ApiClient { | |||
|  * ==================== | ||||
| */ | ||||
| 
 | ||||
|   Future<Result<Owner>> getOwnerById(int id) async { | ||||
|     final url = "https://$apiBasePath/owner/${id.toString()}"; | ||||
|   /// Gets an [Owner] by it's [ownerId] | ||||
|   Future<Result<Owner>> getOwnerById(int ownerId) async { | ||||
|     final url = "https://$apiBasePath/owner/${ownerId.toString()}"; | ||||
|     log.i("Fetching: getOwnerById ($url)"); | ||||
|     final client = Client(); | ||||
|     try { | ||||
|  | @ -577,7 +609,8 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<Owner>> getSectionOwner() async { | ||||
|   /// Get the owner of the current user | ||||
|   Future<Result<Owner>> getOwnerOfUser() async { | ||||
|     final url = "https://$apiBasePath/owner/self"; | ||||
|     log.i("Fetching: getSectionOwner ($url)"); | ||||
|     final client = Client(); | ||||
|  | @ -599,7 +632,7 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Call on `/owners` to get a list of all [Owner]s | ||||
|   /// Get a [List<Owner>] of all [Owner] | ||||
|   Future<Result<List<Owner>>> getOwners() async { | ||||
|     final url = "https://$apiBasePath/owners"; | ||||
|     log.i("Fetching: getOwners ($url)"); | ||||
|  | @ -623,7 +656,7 @@ class ApiClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Adds an owner to the database | ||||
|   /// Adds an [Owner] from its [firstName], [lastName] and [contact] | ||||
|   Future<Result<Owner>> addOwner( | ||||
|     String firstName, | ||||
|     String lastName, | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import 'dart:convert'; | ||||
| import 'dart:ffi'; | ||||
| 
 | ||||
| import 'package:flutter_secure_storage/flutter_secure_storage.dart'; | ||||
| import 'package:logger/logger.dart'; | ||||
|  | @ -7,8 +6,11 @@ import 'package:seshat/config/constants.dart'; | |||
| import 'package:seshat/utils/result.dart'; | ||||
| import "package:http/http.dart"; | ||||
| 
 | ||||
| /// API Client to manage all unauthenticated REST routes | ||||
| class AuthClient { | ||||
|   AuthClient(); | ||||
| 
 | ||||
|   /// Storage to access JWT | ||||
|   FlutterSecureStorage? _secureStorage; | ||||
|   Logger log = Logger( | ||||
|     printer: PrettyPrinter( | ||||
|  | @ -19,10 +21,12 @@ class AuthClient { | |||
|     ), | ||||
|   ); | ||||
| 
 | ||||
|   /// Initializes connection to the [_secureStorage] | ||||
|   Future<void> _initStore() async { | ||||
|     _secureStorage ??= const FlutterSecureStorage(); | ||||
|   } | ||||
| 
 | ||||
|   /// Verifies the validity of the token stored in [_secureStorage] | ||||
|   Future<Result<bool>> hasValidToken() async { | ||||
|     final url = "https://$apiBasePath/token-check"; | ||||
|     log.i("Fetching: hasValidToken ($url)"); | ||||
|  | @ -51,6 +55,7 @@ class AuthClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Logs a user in from its [username] and [password] | ||||
|   Future<Result<String>> login(String username, String password) async { | ||||
|     final url = "https://$apiBasePath/auth"; | ||||
|     log.i("Logging in: $url"); | ||||
|  | @ -85,6 +90,7 @@ class AuthClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the API version of the server | ||||
|   Future<Result<int>> getRemoteApiVersion() async { | ||||
|     final url = "https://$apiBasePath/version"; | ||||
|     log.i("Fetching: getRemoteApiVersion ($url)"); | ||||
|  |  | |||
|  | @ -8,13 +8,23 @@ import 'package:seshat/config/constants.dart'; | |||
| import 'package:seshat/domain/models/owner.dart'; | ||||
| import 'package:web_socket_channel/web_socket_channel.dart'; | ||||
| 
 | ||||
| /// API Client to manages connections to WebSockets | ||||
| class WebsocketClient { | ||||
|   WebSocketChannel? _channel; | ||||
|   /// Storage to access JWT | ||||
|   FlutterSecureStorage? _secureStorage; | ||||
| 
 | ||||
|   /// Raw channel of data from WebSocket | ||||
|   WebSocketChannel? _channel; | ||||
| 
 | ||||
|   /// Global WebSocket Stream | ||||
|   final BehaviorSubject<dynamic> _baseController = BehaviorSubject(); | ||||
| 
 | ||||
|   /// WebSocket Stream dedicated to [Owner] entries | ||||
|   final BehaviorSubject<Owner> _ownersController = BehaviorSubject<Owner>( | ||||
|     sync: true, | ||||
|   ); | ||||
| 
 | ||||
|   /// Subscription to [_baseController] | ||||
|   late final StreamSubscription sub; | ||||
|   Logger log = Logger( | ||||
|     printer: PrettyPrinter( | ||||
|  | @ -25,12 +35,15 @@ class WebsocketClient { | |||
|     ), | ||||
|   ); | ||||
| 
 | ||||
|   /// Gets a stream of [Owner] | ||||
|   Stream<Owner> get owners => _ownersController.stream; | ||||
| 
 | ||||
|   /// Initializes connection to the [_secureStorage] | ||||
|   Future<void> _initStore() async { | ||||
|     _secureStorage ??= const FlutterSecureStorage(); | ||||
|   } | ||||
| 
 | ||||
|   /// Connects to the websocket | ||||
|   Future<void> connect() async { | ||||
|     final url = "wss://$apiBasePath/ws"; | ||||
|     log.i("Webocket: $url"); | ||||
|  | @ -69,11 +82,13 @@ class WebsocketClient { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Disconnects from the websocket | ||||
|   void _handleDisconnect() { | ||||
|     sub.cancel(); | ||||
|     _channel = null; | ||||
|   } | ||||
| 
 | ||||
|   /// Closes all connections | ||||
|   void dispose() { | ||||
|     sub.cancel(); | ||||
|     _channel?.sink.close(); | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ GoRouter router(AuthRepository authRepository) => GoRouter( | |||
|           pageBuilder: (context, state) { | ||||
|             final viewModel = BalViewModel( | ||||
|               balRepository: context.read(), | ||||
|               id: int.parse(state.pathParameters["id"] ?? ""), | ||||
|               selectedBalId: int.parse(state.pathParameters["id"] ?? ""), | ||||
|               ownerRepository: context.read(), | ||||
|             ); | ||||
|             return NoTransitionPage(child: BalPage(viewModel: viewModel)); | ||||
|  |  | |||
|  | @ -38,18 +38,29 @@ class AddViewModel extends ChangeNotifier { | |||
|  * ==================== | ||||
| */ | ||||
| 
 | ||||
|   /// Owner currently selected in the ui | ||||
|   Owner? _currentOwner; | ||||
| 
 | ||||
|   /// Owner currently selected in the ui | ||||
|   Owner? get currentOwner => _currentOwner; | ||||
|   set currentOwner(Owner? owner) { | ||||
|     _currentOwner = owner; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   Owner? _sectionOwner; | ||||
|   Owner? get sectionOwner => _sectionOwner; | ||||
|   /// Owner of the current user | ||||
|   Owner? _ownerOfUser; | ||||
| 
 | ||||
|   /// Owner of the current user | ||||
|   Owner? get ownerOfUser => _ownerOfUser; | ||||
| 
 | ||||
|   /// All the [Owner] | ||||
|   List<Owner> _owners = []; | ||||
| 
 | ||||
|   /// All the [Owner] | ||||
|   List<Owner>? get owners => _owners; | ||||
| 
 | ||||
|   /// Adds an owner from it's [firstName], [lastName] and [contact] | ||||
|   Future<Result<Owner>> addOwner( | ||||
|     String firstName, | ||||
|     String lastName, | ||||
|  | @ -85,8 +96,11 @@ class AddViewModel extends ChangeNotifier { | |||
|  * ================= | ||||
| */ | ||||
| 
 | ||||
|   Bal? _currentBal; | ||||
|   Bal? get currentBal => _currentBal; | ||||
|   /// Ongoing [Bal] | ||||
|   Bal? _ongoingBal; | ||||
| 
 | ||||
|   /// Ongoing [Bal] | ||||
|   Bal? get ongoingBal => _ongoingBal; | ||||
| 
 | ||||
|   /* | ||||
|  * =================== | ||||
|  | @ -94,7 +108,10 @@ class AddViewModel extends ChangeNotifier { | |||
|  * =================== | ||||
| */ | ||||
| 
 | ||||
|   /// Wether to ask for a price | ||||
|   bool _askPrice = true; | ||||
| 
 | ||||
|   /// Wether to ask for a price | ||||
|   bool get askPrice => _askPrice; | ||||
|   set askPrice(bool newValue) { | ||||
|     _askPrice = newValue; | ||||
|  | @ -107,21 +124,26 @@ class AddViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// 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]. | ||||
|   /// Retrieves the book associated with an ean through a [barcode] | ||||
|   Future<Result<Book>> scanBook(BarcodeCapture barcode) async { | ||||
|     var ean = barcode.barcodes.first.rawValue!; | ||||
|     var result = await _bookRepository.getBookByEAN(ean); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<BookInstance>> sendBook( | ||||
|   /// Creates a new Book Instance from its [book], [owner], [bal] and [price] | ||||
|   Future<Result<BookInstance>> sendNewBookInstance( | ||||
|     Book book, | ||||
|     Owner owner, | ||||
|     Bal bal, | ||||
|     double price, | ||||
|   ) async { | ||||
|     return await _bookInstanceRepository.sendBook(book, owner, bal, price); | ||||
|     return await _bookInstanceRepository.sendNewBookInstance( | ||||
|       book, | ||||
|       owner, | ||||
|       bal, | ||||
|       price, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /* | ||||
|  | @ -130,9 +152,11 @@ class AddViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// Command to load the view model | ||||
|   late final Command0 load; | ||||
|   bool isLoaded = false; | ||||
| 
 | ||||
|   /// Manages the loaders | ||||
|   Future<Result<void>> _load() async { | ||||
|     final result1 = await _loadOwners(); | ||||
|     switch (result1) { | ||||
|  | @ -153,11 +177,12 @@ class AddViewModel extends ChangeNotifier { | |||
|     return result2; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads all necessary data about [Bal]s | ||||
|   Future<Result<void>> _loadBal() async { | ||||
|     final result = await _balRepository.getBals(); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _currentBal = result.value | ||||
|         _ongoingBal = result.value | ||||
|             .where((bal) => bal.state == BalState.ongoing) | ||||
|             .firstOrNull; | ||||
|         break; | ||||
|  | @ -168,6 +193,7 @@ class AddViewModel extends ChangeNotifier { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads all the necessary data about [Owner]s | ||||
|   Future<Result<void>> _loadOwners() async { | ||||
|     final result = await _ownerRepository.getOwners(); | ||||
|     switch (result) { | ||||
|  | @ -182,10 +208,10 @@ class AddViewModel extends ChangeNotifier { | |||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     final result2 = await _ownerRepository.sectionOwner; | ||||
|     final result2 = await _ownerRepository.ownerOfUser; | ||||
|     switch (result2) { | ||||
|       case Ok(): | ||||
|         _sectionOwner = result2.value; | ||||
|         _ownerOfUser = result2.value; | ||||
|         break; | ||||
|       default: | ||||
|     } | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ class _AddPageState extends State<AddPage> { | |||
|         listenable: widget.viewModel, | ||||
|         builder: (context, child) => switch (widget.viewModel.isLoaded) { | ||||
|           false => AwaitLoading(), | ||||
|           true => switch (widget.viewModel.currentBal) { | ||||
|           true => switch (widget.viewModel.ongoingBal) { | ||||
|             null => Center( | ||||
|               child: SizedBox( | ||||
|                 width: 300, | ||||
|  |  | |||
|  | @ -123,10 +123,10 @@ class _ConfirmationPopupState extends State<ConfirmationPopup> { | |||
|               _formKey.currentState!.save(); | ||||
|             } | ||||
| 
 | ||||
|             var result = await widget.viewModel.sendBook( | ||||
|             var result = await widget.viewModel.sendNewBookInstance( | ||||
|               widget.book, | ||||
|               widget.viewModel.currentOwner!, | ||||
|               widget.viewModel.currentBal!, | ||||
|               widget.viewModel.ongoingBal!, | ||||
|               price, | ||||
|             ); | ||||
| 
 | ||||
|  | @ -142,7 +142,7 @@ class _ConfirmationPopupState extends State<ConfirmationPopup> { | |||
|                       ), | ||||
|                       content: Text( | ||||
|                         (widget.viewModel.currentOwner!.id == | ||||
|                                 widget.viewModel.sectionOwner!.id) | ||||
|                                 widget.viewModel.ownerOfUser!.id) | ||||
|                             ? "Ce livre appartient à la section. Vous pouvez mettre le code, ou poser une gomette, ..." | ||||
|                             : "Identifiant propriétaire de ce livre. Pensez à l'écrire pour retrouver lae propriétaire du livre lors de la vente ou du retour !", | ||||
|                       ), | ||||
|  |  | |||
|  | @ -13,8 +13,10 @@ class LoginViewModel extends ChangeNotifier { | |||
| 
 | ||||
|   final AuthRepository _authRepository; | ||||
| 
 | ||||
|   /// Command to login with added capabilities | ||||
|   late Command1 login; | ||||
| 
 | ||||
|   /// Logins the user with credentials [(String username, String password)] | ||||
|   Future<Result<void>> _login((String, String) credentials) async { | ||||
|     final (username, password) = credentials; | ||||
|     final result = await _authRepository.login(username, password); | ||||
|  | @ -27,10 +29,12 @@ class LoginViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// Loads all necessary data | ||||
|   late final Command0 load; | ||||
|   bool isLoaded = false; | ||||
|   bool isUpToDate = false; | ||||
| 
 | ||||
|   /// Manages loaders | ||||
|   Future<Result<void>> _load() async { | ||||
|     final result1 = await _loadApiVersion(); | ||||
|     switch (result1) { | ||||
|  | @ -44,6 +48,7 @@ class LoginViewModel extends ChangeNotifier { | |||
|     return result1; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads the current remote api version and compares to local hardcoded [apiVersion] | ||||
|   Future<Result<void>> _loadApiVersion() async { | ||||
|     final result = await _authRepository.getRemoteApiVersion(); | ||||
|     switch (result) { | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import 'package:seshat/utils/result.dart'; | |||
| class BalViewModel extends ChangeNotifier { | ||||
|   BalViewModel({ | ||||
|     required BalRepository balRepository, | ||||
|     required this.id, | ||||
|     required this.selectedBalId, | ||||
|     required OwnerRepository ownerRepository, | ||||
|   }) : _balRepository = balRepository, | ||||
|        _ownerRepository = ownerRepository { | ||||
|  | @ -30,18 +30,26 @@ class BalViewModel extends ChangeNotifier { | |||
|  * ===================== | ||||
| */ | ||||
| 
 | ||||
|   Bal? _bal; | ||||
|   int id; | ||||
|   Bal? get bal => _bal; | ||||
|   /// Selected [Bal] | ||||
|   Bal? _selectedBal; | ||||
| 
 | ||||
|   /// Selected [Bal] | ||||
|   Bal? get selectedBal => _selectedBal; | ||||
| 
 | ||||
|   /// Selected [Bal.id] from path parameters | ||||
|   int selectedBalId; | ||||
| 
 | ||||
|   /// Is one of the [Bal] [BalState.ongoing] | ||||
|   bool isABalOngoing = false; | ||||
| 
 | ||||
|   Future<Result<void>> stopBal(int id) async { | ||||
|   /// Stops a [Bal] | ||||
|   Future<Result<void>> stopBal(int balId) async { | ||||
|     isLoaded = false; | ||||
|     notifyListeners(); | ||||
|     final result = await _balRepository.stopBal(id); | ||||
|     final result = await _balRepository.stopBal(balId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _bal = result.value; | ||||
|         _selectedBal = result.value; | ||||
|         break; | ||||
|       default: | ||||
|     } | ||||
|  | @ -57,14 +65,15 @@ class BalViewModel extends ChangeNotifier { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<Result<void>> startBal(int id) async { | ||||
|   /// Starts a [Bal] | ||||
|   Future<Result<void>> startBal(int balId) async { | ||||
|     if (isABalOngoing) { | ||||
|       return Result.error(Exception("Cannot have multiple BALs ongoing !")); | ||||
|     } | ||||
|     final result = await _balRepository.startBal(id); | ||||
|     final result = await _balRepository.startBal(balId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _bal = result.value; | ||||
|         _selectedBal = result.value; | ||||
|         notifyListeners(); | ||||
|         break; | ||||
|       default: | ||||
|  | @ -72,21 +81,20 @@ class BalViewModel extends ChangeNotifier { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Edits a [Bal]'s [name], [startTime] or [endTime] | ||||
|   Future<Result<void>> editBal( | ||||
|     int id, | ||||
|     String name, | ||||
|     DateTime start, | ||||
|     DateTime end, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final result = await _balRepository.editBal(id, name, start, end); | ||||
|     final result = await _balRepository.editBal(id, name, startTime, endTime); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         debugPrint("\n\n\n\nDID EDIT\n\n\n\n"); | ||||
|         _bal = result.value; | ||||
|         _selectedBal = result.value; | ||||
|         notifyListeners(); | ||||
|         break; | ||||
|       case Error(): | ||||
|         debugPrint("\n\n\n\nERROR: ${result.error}"); | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
|  | @ -99,11 +107,16 @@ class BalViewModel extends ChangeNotifier { | |||
| */ | ||||
| 
 | ||||
|   // Specific to ended state | ||||
| 
 | ||||
|   /// Owners a book or money is owed to | ||||
|   List<ReturnOwner>? owedToOwners; | ||||
|   double? totalOwed; | ||||
| 
 | ||||
|   /// Statistics about the [_selectedBal] | ||||
|   BalStats? stats; | ||||
| 
 | ||||
|   Future<void> applyAccountingOwners( | ||||
|   /// Froms [books], updates [owners] to include all the necessary information | ||||
|   /// See [the api doc](https://bal.ueauvergne.fr/docs/#/bal-api/get_bal_accounting) for more details | ||||
|   Future<void> _updateOwedToOwnersWithBooks( | ||||
|     List<ReturnOwner> owners, | ||||
|     Map<String, Book> books, | ||||
|   ) async { | ||||
|  | @ -124,15 +137,20 @@ class BalViewModel extends ChangeNotifier { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Returns either Books, Money or All ([ReturnType]) to an [Owner] | ||||
|   Future<Result<void>> returnById(ReturnType type, int ownerId) async { | ||||
|     final result = await _balRepository.returnToId(id, ownerId, type); | ||||
|     final result2 = await _balRepository.getAccounting(id); | ||||
|     final result = await _balRepository.returnToId( | ||||
|       selectedBalId, | ||||
|       ownerId, | ||||
|       type, | ||||
|     ); | ||||
|     final result2 = await _balRepository.getAccounting(selectedBalId); | ||||
|     switch (result2) { | ||||
|       case Ok(): | ||||
|         applyAccountingOwners(result2.value.owners, result2.value.books); | ||||
|         _updateOwedToOwnersWithBooks(result2.value.owners, result2.value.books); | ||||
|         break; | ||||
|       case Error(): | ||||
|         debugPrint(result2.error.toString()); | ||||
|         break; | ||||
|     } | ||||
|     notifyListeners(); | ||||
|     return result; | ||||
|  | @ -144,22 +162,25 @@ class BalViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// Loads all the necessary information | ||||
|   late final Command0 load; | ||||
|   bool isLoaded = false; | ||||
| 
 | ||||
|   /// Manages loaders | ||||
|   Future<Result<void>> _load() async { | ||||
|     isABalOngoing = _balRepository.isABalOngoing(); | ||||
|     final result1 = await _loadBal(); | ||||
|     switch (result1) { | ||||
|       case Ok(): | ||||
|         isLoaded = (_bal == null || _bal?.state != BalState.ended) | ||||
|         isLoaded = | ||||
|             (_selectedBal == null || _selectedBal?.state != BalState.ended) | ||||
|             ? true | ||||
|             : false; | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|     if (_bal?.state == BalState.ended) { | ||||
|     if (_selectedBal?.state == BalState.ended) { | ||||
|       final result2 = await _loadEnded(); | ||||
|       switch (result2) { | ||||
|         case Ok(): | ||||
|  | @ -173,11 +194,12 @@ class BalViewModel extends ChangeNotifier { | |||
|     return result1; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads all common [Bal] information | ||||
|   Future<Result<void>> _loadBal() async { | ||||
|     final result = await _balRepository.balById(id); | ||||
|     final result = await _balRepository.balById(selectedBalId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _bal = result.value; | ||||
|         _selectedBal = result.value; | ||||
|         break; | ||||
|       case Error(): | ||||
|         break; | ||||
|  | @ -186,15 +208,16 @@ class BalViewModel extends ChangeNotifier { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads [Bal] information when it is [BalState.ended] | ||||
|   Future<Result<void>> _loadEnded() async { | ||||
|     final result = await _balRepository.getAccountingNoCache(id); | ||||
|     final result = await _balRepository.getAccountingNoCache(selectedBalId); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         applyAccountingOwners(result.value.owners, result.value.books); | ||||
|         _updateOwedToOwnersWithBooks(result.value.owners, result.value.books); | ||||
|         break; | ||||
|       default: | ||||
|     } | ||||
|     final result2 = await _balRepository.getBalStats(id); | ||||
|     final result2 = await _balRepository.getBalStats(selectedBalId); | ||||
|     switch (result2) { | ||||
|       case Ok(): | ||||
|         stats = result2.value; | ||||
|  |  | |||
|  | @ -27,14 +27,14 @@ class _BalPageState extends State<BalPage> { | |||
|             bottomNavigationBar: AppNavigationBar(startIndex: 0), | ||||
|             body: AwaitLoading(), | ||||
|           ), | ||||
|           true => switch (widget.viewModel.bal == null) { | ||||
|           true => switch (widget.viewModel.selectedBal == null) { | ||||
|             true => Scaffold( | ||||
|               bottomNavigationBar: AppNavigationBar(startIndex: 0), | ||||
|               body: Center( | ||||
|                 child: Text("La BAL référencée n'est pas accessible"), | ||||
|               ), | ||||
|             ), | ||||
|             false => switch (widget.viewModel.bal!.state) { | ||||
|             false => switch (widget.viewModel.selectedBal!.state) { | ||||
|               BalState.pending => BalPendingScreen(viewModel: widget.viewModel), | ||||
|               BalState.ongoing => BalOngoingScreen(viewModel: widget.viewModel), | ||||
|               BalState.ended => BalEndedScreen(viewModel: widget.viewModel), | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ class _BalEndedScreenState extends State<BalEndedScreen> | |||
|     return Scaffold( | ||||
|       bottomNavigationBar: AppNavigationBar(startIndex: 0), | ||||
|       appBar: AppBar( | ||||
|         title: Text(widget.viewModel.bal!.name), | ||||
|         title: Text(widget.viewModel.selectedBal!.name), | ||||
|         bottom: TabBar( | ||||
|           controller: tabController, | ||||
|           tabs: [ | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ class BalOngoingScreen extends StatelessWidget { | |||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       bottomNavigationBar: AppNavigationBar(startIndex: 0), | ||||
|       appBar: AppBar(title: Text(viewModel.bal!.name)), | ||||
|       appBar: AppBar(title: Text(viewModel.selectedBal!.name)), | ||||
|       body: Padding( | ||||
|         padding: const EdgeInsets.all(10.0), | ||||
|         child: Column( | ||||
|  | @ -38,7 +38,9 @@ class BalOngoingScreen extends StatelessWidget { | |||
|                           ), | ||||
|                           TextButton( | ||||
|                             onPressed: () async { | ||||
|                               await viewModel.stopBal(viewModel.bal!.id); | ||||
|                               await viewModel.stopBal( | ||||
|                                 viewModel.selectedBal!.id, | ||||
|                               ); | ||||
|                               if (context.mounted) { | ||||
|                                 Navigator.of(context).pop(); | ||||
|                               } | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ class BalPendingScreen extends StatelessWidget { | |||
|     return Scaffold( | ||||
|       bottomNavigationBar: AppNavigationBar(startIndex: 0), | ||||
|       appBar: AppBar( | ||||
|         title: Text(viewModel.bal!.name), | ||||
|         title: Text(viewModel.selectedBal!.name), | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             onPressed: () { | ||||
|  | @ -57,7 +57,9 @@ class BalPendingScreen extends StatelessWidget { | |||
|                           ), | ||||
|                           TextButton( | ||||
|                             onPressed: () async { | ||||
|                               await viewModel.startBal(viewModel.bal!.id); | ||||
|                               await viewModel.startBal( | ||||
|                                 viewModel.selectedBal!.id, | ||||
|                               ); | ||||
|                               if (context.mounted) { | ||||
|                                 Navigator.of(context).pop(); | ||||
|                               } | ||||
|  | @ -96,8 +98,8 @@ class _EditPopup extends State<EditPopup> { | |||
|       firstDate: DateTime(DateTime.now().year - 1), | ||||
|       lastDate: DateTime(DateTime.now().year + 2), | ||||
|       initialDateRange: DateTimeRange( | ||||
|         start: start ?? widget.viewModel.bal!.startTime, | ||||
|         end: end ?? widget.viewModel.bal!.endTime, | ||||
|         start: start ?? widget.viewModel.selectedBal!.startTime, | ||||
|         end: end ?? widget.viewModel.selectedBal!.endTime, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|  | @ -126,7 +128,7 @@ class _EditPopup extends State<EditPopup> { | |||
|                     labelText: "Nom de la BAL", | ||||
|                     border: OutlineInputBorder(), | ||||
|                   ), | ||||
|                   initialValue: widget.viewModel.bal!.name, | ||||
|                   initialValue: widget.viewModel.selectedBal!.name, | ||||
|                   validator: (value) { | ||||
|                     if (value == null || value.isEmpty) { | ||||
|                       return "Veuillez entrer un nom"; | ||||
|  | @ -169,7 +171,7 @@ class _EditPopup extends State<EditPopup> { | |||
|             if (_formKey.currentState!.validate()) { | ||||
|               _formKey.currentState!.save(); | ||||
| 
 | ||||
|               final Bal bal = widget.viewModel.bal!; | ||||
|               final Bal bal = widget.viewModel.selectedBal!; | ||||
| 
 | ||||
|               final result = await widget.viewModel.editBal( | ||||
|                 bal.id, | ||||
|  |  | |||
|  | @ -18,18 +18,25 @@ class HomeViewModel extends ChangeNotifier { | |||
|  * ================= | ||||
| */ | ||||
| 
 | ||||
|   /// [List<Bal>] of all [Bal] | ||||
|   List<Bal> _bals = []; | ||||
| 
 | ||||
|   /// [List<Bal>] of all [Bal] | ||||
|   List<Bal> get bals => _bals; | ||||
| 
 | ||||
|   Bal? _currentBal; | ||||
|   Bal? get currentBal => _currentBal; | ||||
|   /// [Bal] currently [BalState.ongoing] | ||||
|   Bal? _ongoingBal; | ||||
| 
 | ||||
|   /// [Bal] currently [BalState.ongoing] | ||||
|   Bal? get ongoingBal => _ongoingBal; | ||||
| 
 | ||||
|   /// Creates a [Bal] from its [name], [startTime] and [endTime] | ||||
|   Future<Result<void>> createBal( | ||||
|     String name, | ||||
|     DateTime start, | ||||
|     DateTime end, | ||||
|     DateTime startTime, | ||||
|     DateTime endTime, | ||||
|   ) async { | ||||
|     final result = await _balRepository.addBal(name, start, end); | ||||
|     final result = await _balRepository.addBal(name, startTime, endTime); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         final result2 = await _balRepository.getBals(); | ||||
|  | @ -54,9 +61,11 @@ class HomeViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// Command to load all necessary data | ||||
|   late final Command0 load; | ||||
|   bool isLoaded = false; | ||||
| 
 | ||||
|   /// Manages loaders | ||||
|   Future<Result<void>> _load() async { | ||||
|     final result2 = await _loadBal(); | ||||
|     switch (result2) { | ||||
|  | @ -70,12 +79,13 @@ class HomeViewModel extends ChangeNotifier { | |||
|     return result2; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads data about [Bal] | ||||
|   Future<Result<void>> _loadBal() async { | ||||
|     final result = await _balRepository.getBals(); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _bals = result.value..sort((a, b) => a.compareTo(b)); | ||||
|         _currentBal = _bals | ||||
|         _ongoingBal = _bals | ||||
|             .where((bal) => bal.state == BalState.ongoing) | ||||
|             .firstOrNull; | ||||
|         break; | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ class _HomePageState extends State<HomePage> { | |||
|                       : ListView( | ||||
|                           children: [ | ||||
|                             for (Bal bal in widget.viewModel.bals.where( | ||||
|                               (el) => el.id != widget.viewModel.currentBal?.id, | ||||
|                               (el) => el.id != widget.viewModel.ongoingBal?.id, | ||||
|                             )) | ||||
|                               Padding( | ||||
|                                 padding: const EdgeInsets.symmetric( | ||||
|  | @ -81,20 +81,20 @@ class _HomePageState extends State<HomePage> { | |||
|                           ], | ||||
|                         ), | ||||
|                 ), | ||||
|                 switch (widget.viewModel.currentBal == null) { | ||||
|                 switch (widget.viewModel.ongoingBal == null) { | ||||
|                   true => SizedBox(), | ||||
|                   false => Padding( | ||||
|                     padding: const EdgeInsets.symmetric(horizontal: 10.0), | ||||
|                     child: Card( | ||||
|                       child: ListTile( | ||||
|                         leading: Icon(Icons.event_available), | ||||
|                         title: Text(widget.viewModel.currentBal!.name), | ||||
|                         title: Text(widget.viewModel.ongoingBal!.name), | ||||
|                         subtitle: Text("BAL en cours"), | ||||
|                         trailing: IconButton( | ||||
|                           onPressed: () { | ||||
|                             _moveToBal( | ||||
|                               context, | ||||
|                               widget.viewModel.currentBal!.id, | ||||
|                               widget.viewModel.ongoingBal!.id, | ||||
|                             ); | ||||
|                           }, | ||||
|                           icon: Icon(Icons.arrow_forward), | ||||
|  |  | |||
|  | @ -31,10 +31,13 @@ class SellViewModel extends ChangeNotifier { | |||
|   final BookRepository _bookRepository; | ||||
|   final OwnerRepository _ownerRepository; | ||||
| 
 | ||||
|   bool _showScan = false; | ||||
|   bool get showScan => _showScan; | ||||
|   set showScan(bool newValue) { | ||||
|     _showScan = newValue; | ||||
|   /// Wether to show the scan screen | ||||
|   bool _showScanScreen = false; | ||||
| 
 | ||||
|   /// Wether to show the scan screen | ||||
|   bool get showScanScreen => _showScanScreen; | ||||
|   set showScanScreen(bool newValue) { | ||||
|     _showScanScreen = newValue; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -44,56 +47,66 @@ class SellViewModel extends ChangeNotifier { | |||
|  * =============================== | ||||
| */ | ||||
| 
 | ||||
|   final List<BookStack> _soldBooks = []; | ||||
|   List<BookStack> get soldBooks => _soldBooks; | ||||
|   /// Books in the sell | ||||
|   final List<BookStack> _booksInSell = []; | ||||
| 
 | ||||
|   /// Books in the sell | ||||
|   List<BookStack> get booksInSell => _booksInSell; | ||||
| 
 | ||||
|   /// Books scanned on the scan screen | ||||
|   final List<BookStack> _scannedBooks = []; | ||||
| 
 | ||||
|   /// Books scanned on the scan screen | ||||
|   List<BookStack> get scannedBooks => _scannedBooks; | ||||
| 
 | ||||
|   bool isScanLoaded = false; | ||||
|   bool isSendingSell = false; | ||||
|   double minimumAmount = 0; | ||||
|   double minimumAmountToPay = 0; | ||||
| 
 | ||||
|   void sellBook(BookStack addedBook) { | ||||
|     minimumAmount += addedBook.instance.price; | ||||
|     _soldBooks.add(addedBook); | ||||
|   /// Adds a book to the [_booksInSell] | ||||
|   void addBookToSell(BookStack bookToAdd) { | ||||
|     minimumAmountToPay += bookToAdd.instance.price; | ||||
|     _booksInSell.add(bookToAdd); | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   void sendSell(double givenAmount) async { | ||||
|   /// Sends the sell | ||||
|   void sendSell(double givenMoney) async { | ||||
|     isSendingSell = true; | ||||
|     notifyListeners(); | ||||
|     List<BookInstance> toSend = []; | ||||
|     int nbOfPl = 0; | ||||
|     for (BookStack book in _soldBooks) { | ||||
|     List<BookInstance> booksToSend = []; | ||||
|     int numberOfPL = 0; | ||||
|     for (BookStack book in _booksInSell) { | ||||
|       if (book.instance.price != 0) { | ||||
|         book.instance.soldPrice = book.instance.price; | ||||
|         givenAmount -= book.instance.price; | ||||
|         toSend.add(book.instance); | ||||
|         givenMoney -= book.instance.price; | ||||
|         booksToSend.add(book.instance); | ||||
|       } else { | ||||
|         nbOfPl++; | ||||
|         numberOfPL++; | ||||
|       } | ||||
|     } | ||||
|     if (nbOfPl != 0) { | ||||
|       double amountPerPl = givenAmount / nbOfPl; | ||||
|       for (BookStack book in _soldBooks) { | ||||
|     if (numberOfPL != 0) { | ||||
|       double moneyPerPL = givenMoney / numberOfPL; | ||||
|       for (BookStack book in _booksInSell) { | ||||
|         if (book.instance.price == 0) { | ||||
|           book.instance.soldPrice = amountPerPl; | ||||
|           toSend.add(book.instance); | ||||
|           book.instance.soldPrice = moneyPerPL; | ||||
|           booksToSend.add(book.instance); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     await _bookInstanceRepository.sellBooks(toSend); | ||||
|     _soldBooks.clear(); | ||||
|     await _bookInstanceRepository.sellBooks(booksToSend); | ||||
|     _booksInSell.clear(); | ||||
|     isSendingSell = false; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   void deleteBook(int id) { | ||||
|     _soldBooks.removeWhere((book) => book.instance.id == id); | ||||
|   /// Removes a book from the sell | ||||
|   void removeBookFromSell(int bookId) { | ||||
|     _booksInSell.removeWhere((book) => book.instance.id == bookId); | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   /// Search a book by [title] or [author] | ||||
|   Future<void> searchBook(String title, String author) async { | ||||
|     Bal? bal = await _balRepository.ongoingBal(); | ||||
|     isScanLoaded = false; | ||||
|  | @ -106,15 +119,21 @@ class SellViewModel extends ChangeNotifier { | |||
|     ); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         // For each result value, you need to complete some values | ||||
|         for (SearchResult searchResult in result.value) { | ||||
|           // In case you get a book that's actually not available | ||||
|           if (searchResult.instance.available == false) { | ||||
|             continue; | ||||
|           } | ||||
|           if (_soldBooks | ||||
| 
 | ||||
|           // In case the instance is already in the sell | ||||
|           if (_booksInSell | ||||
|               .where((book) => book.instance.id == searchResult.instance.id) | ||||
|               .isNotEmpty) { | ||||
|             continue; | ||||
|           } | ||||
| 
 | ||||
|           // Search for the owner | ||||
|           Owner owner; | ||||
|           final result2 = await _ownerRepository.getOwnerById( | ||||
|             searchResult.instance.ownerId, | ||||
|  | @ -126,6 +145,7 @@ class SellViewModel extends ChangeNotifier { | |||
|             case Error(): | ||||
|               continue; | ||||
|           } | ||||
| 
 | ||||
|           _scannedBooks.add( | ||||
|             BookStack(searchResult.book, searchResult.instance, owner), | ||||
|           ); | ||||
|  | @ -140,18 +160,19 @@ class SellViewModel extends ChangeNotifier { | |||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   /// Gets [BookInstance]s from its ean in a [barcode] | ||||
|   Future<void> scanBook(BarcodeCapture barcode) async { | ||||
|     isScanLoaded = false; | ||||
|     int ean = int.parse(barcode.barcodes.first.rawValue!); | ||||
|     Bal? bal = await _balRepository.ongoingBal(); | ||||
|     Bal? ongoingBal = await _balRepository.ongoingBal(); | ||||
|     _scannedBooks.clear(); | ||||
| 
 | ||||
|     final result = await _bookInstanceRepository.getByEan(bal!.id, ean); | ||||
|     switch (result) { | ||||
|     final result1 = await _bookInstanceRepository.getByEan(ongoingBal!.id, ean); | ||||
|     switch (result1) { | ||||
|       case Ok(): | ||||
|         Book book; | ||||
|         final result2 = await _bookRepository.getBookById( | ||||
|           result.value.first.bookId, | ||||
|           result1.value.first.bookId, | ||||
|         ); | ||||
|         switch (result2) { | ||||
|           case Ok(): | ||||
|  | @ -160,15 +181,22 @@ class SellViewModel extends ChangeNotifier { | |||
|           case Error(): | ||||
|             return; | ||||
|         } | ||||
|         for (BookInstance instance in result.value) { | ||||
| 
 | ||||
|         // For each result value, you need to complete some values | ||||
|         for (BookInstance instance in result1.value) { | ||||
|           // In case you get a book that's actually not available | ||||
|           if (instance.available == false) { | ||||
|             continue; | ||||
|           } | ||||
|           if (_soldBooks | ||||
| 
 | ||||
|           // In case the instance is already in the sell | ||||
|           if (_booksInSell | ||||
|               .where((book) => book.instance.id == instance.id) | ||||
|               .isNotEmpty) { | ||||
|             continue; | ||||
|           } | ||||
| 
 | ||||
|           // Search for the owner | ||||
|           Owner owner; | ||||
|           final result3 = await _ownerRepository.getOwnerById(instance.ownerId); | ||||
|           switch (result3) { | ||||
|  | @ -178,6 +206,7 @@ class SellViewModel extends ChangeNotifier { | |||
|             case Error(): | ||||
|               continue; | ||||
|           } | ||||
| 
 | ||||
|           _scannedBooks.add(BookStack(book, instance, owner)); | ||||
|         } | ||||
|         break; | ||||
|  | @ -196,8 +225,11 @@ class SellViewModel extends ChangeNotifier { | |||
|  * ================= | ||||
| */ | ||||
| 
 | ||||
|   Bal? _currentBal; | ||||
|   get currentBal => _currentBal; | ||||
|   /// The currently ongoing [Bal] | ||||
|   Bal? _ongoingBal; | ||||
| 
 | ||||
|   /// The currently ongoing [Bal] | ||||
|   get ongoingBal => _ongoingBal; | ||||
| 
 | ||||
|   /* | ||||
|  * ================================= | ||||
|  | @ -205,9 +237,11 @@ class SellViewModel extends ChangeNotifier { | |||
|  * ================================= | ||||
| */ | ||||
| 
 | ||||
|   /// Command to load necessary data | ||||
|   late final Command0 load; | ||||
|   bool isLoaded = false; | ||||
| 
 | ||||
|   /// Manages loaders | ||||
|   Future<Result<void>> _load() async { | ||||
|     final result1 = await _loadBal(); | ||||
|     switch (result1) { | ||||
|  | @ -221,11 +255,12 @@ class SellViewModel extends ChangeNotifier { | |||
|     return result1; | ||||
|   } | ||||
| 
 | ||||
|   /// Loads information about [Bal] | ||||
|   Future<Result<void>> _loadBal() async { | ||||
|     final result = await _balRepository.getBals(); | ||||
|     switch (result) { | ||||
|       case Ok(): | ||||
|         _currentBal = result.value | ||||
|         _ongoingBal = result.value | ||||
|             .where((bal) => bal.state == BalState.ongoing) | ||||
|             .firstOrNull; | ||||
|         break; | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ class _ScanScreenState extends State<ScanScreen> { | |||
|             children: [ | ||||
|               IconButton( | ||||
|                 onPressed: () { | ||||
|                   widget.viewModel.showScan = false; | ||||
|                   widget.viewModel.showScanScreen = false; | ||||
|                 }, | ||||
|                 icon: Icon(Icons.arrow_back), | ||||
|               ), | ||||
|  |  | |||
|  | @ -34,9 +34,9 @@ class SellChoicePopup extends StatelessWidget { | |||
|                       child: Card( | ||||
|                         child: InkWell( | ||||
|                           onTap: () { | ||||
|                             viewModel.sellBook(book); | ||||
|                             viewModel.addBookToSell(book); | ||||
|                             Navigator.of(context).pop(); | ||||
|                             viewModel.showScan = false; | ||||
|                             viewModel.showScanScreen = false; | ||||
|                           }, | ||||
|                           child: ListTile( | ||||
|                             leading: Text( | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ class _SellPageState extends State<SellPage> { | |||
|         builder: (context, child) { | ||||
|           return switch (widget.viewModel.isLoaded) { | ||||
|             false => AwaitLoading(), | ||||
|             true => switch (widget.viewModel.currentBal) { | ||||
|             true => switch (widget.viewModel.ongoingBal) { | ||||
|               null => Center( | ||||
|                 child: SizedBox( | ||||
|                   width: 300, | ||||
|  | @ -80,7 +80,7 @@ class _SellPageState extends State<SellPage> { | |||
|                                         ? Center(child: Text("Aucun")) | ||||
|                                         : SizedBox(), | ||||
|                                     for (BookStack book | ||||
|                                         in widget.viewModel.soldBooks) | ||||
|                                         in widget.viewModel.booksInSell) | ||||
|                                       Padding( | ||||
|                                         padding: const EdgeInsets.symmetric( | ||||
|                                           horizontal: 15, | ||||
|  | @ -99,9 +99,10 @@ class _SellPageState extends State<SellPage> { | |||
|                                             ), | ||||
|                                             trailing: IconButton( | ||||
|                                               onPressed: () { | ||||
|                                                 widget.viewModel.deleteBook( | ||||
|                                                   book.instance.id, | ||||
|                                                 ); | ||||
|                                                 widget.viewModel | ||||
|                                                     .removeBookFromSell( | ||||
|                                                       book.instance.id, | ||||
|                                                     ); | ||||
|                                               }, | ||||
|                                               icon: Icon(Icons.delete), | ||||
|                                             ), | ||||
|  | @ -113,7 +114,7 @@ class _SellPageState extends State<SellPage> { | |||
|                         ), | ||||
|                         SizedBox(height: 40), | ||||
|                         Text( | ||||
|                           "Montant minimum à payer : ${widget.viewModel.minimumAmount.toString()}€", | ||||
|                           "Montant minimum à payer : ${widget.viewModel.minimumAmountToPay.toString()}€", | ||||
|                         ), | ||||
|                         Padding( | ||||
|                           padding: const EdgeInsets.symmetric(horizontal: 60.0), | ||||
|  | @ -167,7 +168,7 @@ class _SellPageState extends State<SellPage> { | |||
|                                 } else if (double.parse( | ||||
|                                       price.text.replaceFirst(",", "."), | ||||
|                                     ) < | ||||
|                                     widget.viewModel.minimumAmount) { | ||||
|                                     widget.viewModel.minimumAmountToPay) { | ||||
|                                   ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                     SnackBar( | ||||
|                                       content: Text( | ||||
|  | @ -202,7 +203,7 @@ class _SellPageState extends State<SellPage> { | |||
|                             SizedBox(width: 70), | ||||
|                             IconButton( | ||||
|                               onPressed: () { | ||||
|                                 widget.viewModel.showScan = true; | ||||
|                                 widget.viewModel.showScanScreen = true; | ||||
|                               }, | ||||
|                               icon: Icon(Icons.add), | ||||
|                               style: ButtonStyle( | ||||
|  | @ -216,7 +217,7 @@ class _SellPageState extends State<SellPage> { | |||
|                       ], | ||||
|                     ), | ||||
|                   ), | ||||
|                   (widget.viewModel.showScan) | ||||
|                   (widget.viewModel.showScanScreen) | ||||
|                       ? ScanScreen(viewModel: widget.viewModel) | ||||
|                       : SizedBox(), | ||||
|                 ], | ||||
|  |  | |||
		Reference in a new issue
	
	 alzalia1
						alzalia1