From dad000a1b911967a5138c289425651eaabd011d5 Mon Sep 17 00:00:00 2001 From: alzalia1 Date: Sat, 23 Aug 2025 12:35:36 +0200 Subject: [PATCH] fix: continuing error managment and documentation --- lib/data/repositories/auth_repository.dart | 6 +- lib/data/repositories/bal_repository.dart | 70 ++++++++---- .../book_instance_repository.dart | 9 +- lib/data/repositories/book_repository.dart | 7 +- lib/data/repositories/owner_repository.dart | 31 +++-- lib/data/services/api_client.dart | 91 ++++++++++----- lib/data/services/auth_client.dart | 8 +- lib/data/services/websocket_client.dart | 17 ++- lib/routing/router.dart | 2 +- .../add_page/view_model/add_view_model.dart | 48 ++++++-- lib/ui/add_page/widgets/add_page.dart | 2 +- .../add_page/widgets/confirmation_popup.dart | 6 +- lib/ui/auth/viewmodel/login_view_model.dart | 5 + .../bal_page/view_model/bal_view_model.dart | 81 ++++++++----- lib/ui/bal_page/widget/bal_page.dart | 4 +- .../widget/ended/bal_ended_screen.dart | 2 +- .../widget/ongoing/bal_ongoing_screen.dart | 6 +- .../widget/pending/bal_pending_screen.dart | 14 ++- .../home_page/view_model/home_view_model.dart | 22 +++- lib/ui/home_page/widgets/home_page.dart | 8 +- .../sell_page/view_model/sell_view_model.dart | 107 ++++++++++++------ lib/ui/sell_page/widgets/scan_screen.dart | 2 +- .../sell_page/widgets/sell_choice_popup.dart | 4 +- lib/ui/sell_page/widgets/sell_page.dart | 19 ++-- 24 files changed, 389 insertions(+), 182 deletions(-) diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 2b56ddb..c55d564 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -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 get isLoggedIn async { if (_isAuthenticated != null) { return _isAuthenticated!; @@ -25,6 +27,7 @@ class AuthRepository extends ChangeNotifier { } } + /// Logs in the user Future> 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> getRemoteApiVersion() async { return await _authClient.getRemoteApiVersion(); } diff --git a/lib/data/repositories/bal_repository.dart b/lib/data/repositories/bal_repository.dart index ccce2e4..97bc023 100644 --- a/lib/data/repositories/bal_repository.dart +++ b/lib/data/repositories/bal_repository.dart @@ -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? _bals; - Accounting? accounting; + /// [List] of all the user's [Bal] + List? _bals; + + /// [Accounting] of [Bal], mapped by [Bal] id + final Map _accountingMap = {}; + + /// Gets a list of all [Bal] from cache or remote Future>> 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>> _getBalsNoCache() async { final result = await _apiClient.getBals(); switch (result) { @@ -37,15 +44,16 @@ class BalRepository { } } - Future> balById(int id) async { + /// Gets a [Bal] by [balId], either from cache or remote + Future> 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 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> stopBal(int id) async { final result = await _apiClient.stopBal(id); _getBalsNoCache(); return result; } + /// Starts a [Bal] and refresh cache Future> 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> 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> 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> addBal( + String name, + DateTime startTime, + DateTime endTime, + ) async { + final result = await _apiClient.addBal(name, startTime, endTime); await _getBalsNoCache(); return result; } - Future> getBalStats(int id) async { - return _apiClient.getBalStats(id); + /// Gets a [BalStats] from its [balId] + Future> getBalStats(int balId) async { + return _apiClient.getBalStats(balId); } + /// Get [Accounting] of a [Bal] from remote only Future> 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> 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> 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; diff --git a/lib/data/repositories/book_instance_repository.dart b/lib/data/repositories/book_instance_repository.dart index 4a07167..7a60d19 100644 --- a/lib/data/repositories/book_instance_repository.dart +++ b/lib/data/repositories/book_instance_repository.dart @@ -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] from an [ean] Future>> getByEan(int balId, int ean) async { return await _apiClient.getBookInstanceByEAN(balId, ean); } + /// Gets a [List] from a [title] and [author] Future>> getBySearch( int balId, String title, @@ -24,15 +27,17 @@ class BookInstanceRepository { return await _apiClient.getBookInstanceBySearch(balId, title, author); } - Future> sendBook( + /// Sends a new [BookInstance]'s [book], [owner], [bal] and [price] + Future> 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] Future> sellBooks(List books) async { Map res = {}; for (BookInstance instance in books) { diff --git a/lib/data/repositories/book_repository.dart b/lib/data/repositories/book_repository.dart index 984d85b..5d38be1 100644 --- a/lib/data/repositories/book_repository.dart +++ b/lib/data/repositories/book_repository.dart @@ -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> getBookByEAN(String ean) async { return await _apiClient.getBookByEAN(ean); } - Future> getBookById(int id) async { - return await _apiClient.getBookById(id); + /// Gets a [Book] by its [bookId] + Future> getBookById(int bookId) async { + return await _apiClient.getBookById(bookId); } } diff --git a/lib/data/repositories/owner_repository.dart b/lib/data/repositories/owner_repository.dart index 5020123..817bea0 100644 --- a/lib/data/repositories/owner_repository.dart +++ b/lib/data/repositories/owner_repository.dart @@ -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? _cachedOwners; - Owner? _sectionOwner; - Future> get sectionOwner async { - if (_sectionOwner != null) { - return Result.ok(_sectionOwner!); + /// [StreamSubscription] to the [Stream] for [_wsClient] + late final StreamSubscription sub; + + /// [List] of owners, updated by [_wsClient] + List? _cachedOwners; + + /// [Owner] of the current user + Owner? _ownerOfUser; + + /// [Owner] of the current user + Future> 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> getOwnerById(int id) async { + /// Gets an [Owner] from its [ownerId] + Future> 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]. diff --git a/lib/data/services/api_client.dart b/lib/data/services/api_client.dart index 0310bea..c638d4c 100644 --- a/lib/data/services/api_client.dart +++ b/lib/data/services/api_client.dart @@ -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 _initStore() async { _secureStorage ??= const FlutterSecureStorage(); } + /// Generates authorization headers and option [additionalHeaders] Future> _getHeaders([ Map? additionalHeaders, ]) async { @@ -55,6 +62,7 @@ class ApiClient { * ======================== */ + /// Gets data about a BAL's needed returns Future> 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> 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> getBalStats(int id) async { - final url = "https://$apiBasePath/bal/${id.toString()}/stats"; + /// Get stats about a BAL's performance + Future> 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> stopBal(int id) async { - final url = "https://$apiBasePath/bal/${id.toString()}/stop"; + /// Stops a BAL, putting it's [BalState] to [BalState.ended] + Future> 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> startBal(int id) async { - final url = "https://$apiBasePath/bal/${id.toString()}/start"; + /// Starts a BAL, putting it's [BalState] to [BalState.ongoing] + Future> 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> 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> getBalById(int id) async { - final url = "https://$apiBasePath/bal/${id.toString()}"; + /// Gets a [Bal] from it's [balId] + Future> 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> addBal(String name, DateTime start, DateTime end) async { + /// Adds a [Bal] from it's [name], [startTime] and [endTime] + Future> 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] of all [Bal] Future>> getBals() async { final url = "https://$apiBasePath/bals"; log.i("Fetching: getBals ($url)"); @@ -328,7 +349,8 @@ class ApiClient { } } - Future> getCurrentBal() async { + /// Gets the ongoing BAL for the user + Future> getOngoingBal() async { final url = "https://$apiBasePath/bal/current"; log.i("Fetching: getCurrentBal ($url)"); final client = Client(); @@ -356,8 +378,9 @@ class ApiClient { * =================== */ - Future> getBookById(int id) async { - final url = "https://$apiBasePath/book/id/${id.toString()}"; + /// Gets a [Book] by its [bookId] + Future> 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> 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>> getBookInstanceBySearch( int balId, String title, @@ -445,6 +470,7 @@ class ApiClient { } } + /// Gets a [BookInstance] from it's [ean] Future>> 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> sellBooks(Map books) async { final url = "https://$apiBasePath/book_instance/sell/bulk"; log.i("Fetching: sellBooks ($url)"); @@ -507,7 +537,8 @@ class ApiClient { } } - Future> sendBook( + /// Creates a new [BookInstance] from it's [book], it's [owner], it's [bal] and it's [price] + Future> sendNewBookInstance( Book book, Owner owner, Bal bal, @@ -551,8 +582,9 @@ class ApiClient { * ==================== */ - Future> getOwnerById(int id) async { - final url = "https://$apiBasePath/owner/${id.toString()}"; + /// Gets an [Owner] by it's [ownerId] + Future> 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> getSectionOwner() async { + /// Get the owner of the current user + Future> 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] of all [Owner] Future>> 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> addOwner( String firstName, String lastName, diff --git a/lib/data/services/auth_client.dart b/lib/data/services/auth_client.dart index 8b5653b..3dc8496 100644 --- a/lib/data/services/auth_client.dart +++ b/lib/data/services/auth_client.dart @@ -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 _initStore() async { _secureStorage ??= const FlutterSecureStorage(); } + /// Verifies the validity of the token stored in [_secureStorage] Future> 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> 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> getRemoteApiVersion() async { final url = "https://$apiBasePath/version"; log.i("Fetching: getRemoteApiVersion ($url)"); diff --git a/lib/data/services/websocket_client.dart b/lib/data/services/websocket_client.dart index 89cf8b9..1d85989 100644 --- a/lib/data/services/websocket_client.dart +++ b/lib/data/services/websocket_client.dart @@ -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 _baseController = BehaviorSubject(); + + /// WebSocket Stream dedicated to [Owner] entries final BehaviorSubject _ownersController = BehaviorSubject( 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 get owners => _ownersController.stream; + /// Initializes connection to the [_secureStorage] Future _initStore() async { _secureStorage ??= const FlutterSecureStorage(); } + /// Connects to the websocket Future 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(); diff --git a/lib/routing/router.dart b/lib/routing/router.dart index 3c3cdb8..fd530ec 100644 --- a/lib/routing/router.dart +++ b/lib/routing/router.dart @@ -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)); diff --git a/lib/ui/add_page/view_model/add_view_model.dart b/lib/ui/add_page/view_model/add_view_model.dart index 0e685ba..9334ea7 100644 --- a/lib/ui/add_page/view_model/add_view_model.dart +++ b/lib/ui/add_page/view_model/add_view_model.dart @@ -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 _owners = []; + + /// All the [Owner] List? get owners => _owners; + + /// Adds an owner from it's [firstName], [lastName] and [contact] Future> 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> scanBook(BarcodeCapture barcode) async { var ean = barcode.barcodes.first.rawValue!; var result = await _bookRepository.getBookByEAN(ean); return result; } - Future> sendBook( + /// Creates a new Book Instance from its [book], [owner], [bal] and [price] + Future> 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> _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> _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> _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: } diff --git a/lib/ui/add_page/widgets/add_page.dart b/lib/ui/add_page/widgets/add_page.dart index d66e827..4d22005 100644 --- a/lib/ui/add_page/widgets/add_page.dart +++ b/lib/ui/add_page/widgets/add_page.dart @@ -45,7 +45,7 @@ class _AddPageState extends State { 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, diff --git a/lib/ui/add_page/widgets/confirmation_popup.dart b/lib/ui/add_page/widgets/confirmation_popup.dart index 776dc5b..e57f02c 100644 --- a/lib/ui/add_page/widgets/confirmation_popup.dart +++ b/lib/ui/add_page/widgets/confirmation_popup.dart @@ -123,10 +123,10 @@ class _ConfirmationPopupState extends State { _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 { ), 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 !", ), diff --git a/lib/ui/auth/viewmodel/login_view_model.dart b/lib/ui/auth/viewmodel/login_view_model.dart index 190ea46..51179f0 100644 --- a/lib/ui/auth/viewmodel/login_view_model.dart +++ b/lib/ui/auth/viewmodel/login_view_model.dart @@ -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> _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> _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> _loadApiVersion() async { final result = await _authRepository.getRemoteApiVersion(); switch (result) { diff --git a/lib/ui/bal_page/view_model/bal_view_model.dart b/lib/ui/bal_page/view_model/bal_view_model.dart index ad5e47d..c3cdbb9 100644 --- a/lib/ui/bal_page/view_model/bal_view_model.dart +++ b/lib/ui/bal_page/view_model/bal_view_model.dart @@ -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> stopBal(int id) async { + /// Stops a [Bal] + Future> 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> startBal(int id) async { + /// Starts a [Bal] + Future> 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> 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? owedToOwners; - double? totalOwed; + + /// Statistics about the [_selectedBal] BalStats? stats; - Future 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 _updateOwedToOwnersWithBooks( List owners, Map books, ) async { @@ -124,15 +137,20 @@ class BalViewModel extends ChangeNotifier { } } + /// Returns either Books, Money or All ([ReturnType]) to an [Owner] Future> 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> _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> _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> _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; diff --git a/lib/ui/bal_page/widget/bal_page.dart b/lib/ui/bal_page/widget/bal_page.dart index 2347dc3..e7e3467 100644 --- a/lib/ui/bal_page/widget/bal_page.dart +++ b/lib/ui/bal_page/widget/bal_page.dart @@ -27,14 +27,14 @@ class _BalPageState extends State { 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), diff --git a/lib/ui/bal_page/widget/ended/bal_ended_screen.dart b/lib/ui/bal_page/widget/ended/bal_ended_screen.dart index 6f5043c..2b62688 100644 --- a/lib/ui/bal_page/widget/ended/bal_ended_screen.dart +++ b/lib/ui/bal_page/widget/ended/bal_ended_screen.dart @@ -30,7 +30,7 @@ class _BalEndedScreenState extends State 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: [ diff --git a/lib/ui/bal_page/widget/ongoing/bal_ongoing_screen.dart b/lib/ui/bal_page/widget/ongoing/bal_ongoing_screen.dart index a9ca41e..db9fd01 100644 --- a/lib/ui/bal_page/widget/ongoing/bal_ongoing_screen.dart +++ b/lib/ui/bal_page/widget/ongoing/bal_ongoing_screen.dart @@ -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(); } diff --git a/lib/ui/bal_page/widget/pending/bal_pending_screen.dart b/lib/ui/bal_page/widget/pending/bal_pending_screen.dart index 2e57c22..e5d7164 100644 --- a/lib/ui/bal_page/widget/pending/bal_pending_screen.dart +++ b/lib/ui/bal_page/widget/pending/bal_pending_screen.dart @@ -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 { 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 { 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 { 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, diff --git a/lib/ui/home_page/view_model/home_view_model.dart b/lib/ui/home_page/view_model/home_view_model.dart index 29ffeda..e6b68d2 100644 --- a/lib/ui/home_page/view_model/home_view_model.dart +++ b/lib/ui/home_page/view_model/home_view_model.dart @@ -18,18 +18,25 @@ class HomeViewModel extends ChangeNotifier { * ================= */ + /// [List] of all [Bal] List _bals = []; + + /// [List] of all [Bal] List 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> 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> _load() async { final result2 = await _loadBal(); switch (result2) { @@ -70,12 +79,13 @@ class HomeViewModel extends ChangeNotifier { return result2; } + /// Loads data about [Bal] Future> _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; diff --git a/lib/ui/home_page/widgets/home_page.dart b/lib/ui/home_page/widgets/home_page.dart index adc6684..2f56dbd 100644 --- a/lib/ui/home_page/widgets/home_page.dart +++ b/lib/ui/home_page/widgets/home_page.dart @@ -38,7 +38,7 @@ class _HomePageState extends State { : 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 { ], ), ), - 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), 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 549b328..77b0393 100644 --- a/lib/ui/sell_page/view_model/sell_view_model.dart +++ b/lib/ui/sell_page/view_model/sell_view_model.dart @@ -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 _soldBooks = []; - List get soldBooks => _soldBooks; + /// Books in the sell + final List _booksInSell = []; + /// Books in the sell + List get booksInSell => _booksInSell; + + /// Books scanned on the scan screen final List _scannedBooks = []; + + /// Books scanned on the scan screen List 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 toSend = []; - int nbOfPl = 0; - for (BookStack book in _soldBooks) { + List 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 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 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> _load() async { final result1 = await _loadBal(); switch (result1) { @@ -221,11 +255,12 @@ class SellViewModel extends ChangeNotifier { return result1; } + /// Loads information about [Bal] Future> _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; diff --git a/lib/ui/sell_page/widgets/scan_screen.dart b/lib/ui/sell_page/widgets/scan_screen.dart index ceb9920..3036fb8 100644 --- a/lib/ui/sell_page/widgets/scan_screen.dart +++ b/lib/ui/sell_page/widgets/scan_screen.dart @@ -65,7 +65,7 @@ class _ScanScreenState extends State { children: [ IconButton( onPressed: () { - widget.viewModel.showScan = false; + widget.viewModel.showScanScreen = false; }, icon: Icon(Icons.arrow_back), ), diff --git a/lib/ui/sell_page/widgets/sell_choice_popup.dart b/lib/ui/sell_page/widgets/sell_choice_popup.dart index 1931a86..f96bb93 100644 --- a/lib/ui/sell_page/widgets/sell_choice_popup.dart +++ b/lib/ui/sell_page/widgets/sell_choice_popup.dart @@ -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( diff --git a/lib/ui/sell_page/widgets/sell_page.dart b/lib/ui/sell_page/widgets/sell_page.dart index b04ae81..784ff23 100644 --- a/lib/ui/sell_page/widgets/sell_page.dart +++ b/lib/ui/sell_page/widgets/sell_page.dart @@ -28,7 +28,7 @@ class _SellPageState extends State { 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 { ? 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 { ), 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 { ), 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 { } 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 { 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 { ], ), ), - (widget.viewModel.showScan) + (widget.viewModel.showScanScreen) ? ScanScreen(viewModel: widget.viewModel) : SizedBox(), ],