diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml deleted file mode 100644 index 7008872..0000000 --- a/.forgejo/workflows/deploy.yml +++ /dev/null @@ -1,18 +0,0 @@ -on: - release: - types: [published] -jobs: - test: - runs-on: docker - steps: - - run: apt update && apt install -y sshpass jq - - uses: actions/checkout@v4 - - name: Set up Flutter - uses: https://github.com/subosito/flutter-action@v2 - with: - channel: stable - flutter-version: 3.35.1 - cache: true - - run: git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.35.1-x64 - - run: flutter build web --release --wasm --base-href /app/ - - run: sshpass -p "${{ secrets.DEPLOY_PASSWORD }}" scp -o StrictHostKeyChecking=accept-new -rp build/web/* ${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_ADDRESS }}:${{ secrets.DEPLOY_PATH }} diff --git a/README.md b/README.md index a5a46b7..304117b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!WARNING] -> This repo has been moved to [illes](https://git.illes.fr/UEAuvergne/Seshat). - # seshat Client android/iOS/web, écrit en dart x flutter, pour Alexandria. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 80cd4ad..d2ea4a2 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "fr.ueauvergne.seshat" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdkVersion(24) + minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html index d098403..f7fc0b1 100644 --- a/android/build/reports/problems/problems-report.html +++ b/android/build/reports/problems/problems-report.html @@ -650,7 +650,7 @@ code + .copy-button { diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index c55d564..2b56ddb 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -2,7 +2,6 @@ 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; @@ -10,7 +9,6 @@ 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!; @@ -27,7 +25,6 @@ class AuthRepository extends ChangeNotifier { } } - /// Logs in the user Future> login(String username, String password) async { try { final result = await _authClient.login(username, password); @@ -36,14 +33,13 @@ class AuthRepository extends ChangeNotifier { _isAuthenticated = true; return Result.ok(()); case Error(): - return result; + return Result.error(result.error); } } 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 97bc023..ccce2e4 100644 --- a/lib/data/repositories/bal_repository.dart +++ b/lib/data/repositories/bal_repository.dart @@ -5,19 +5,13 @@ 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] of all the user's [Bal] List? _bals; + Accounting? accounting; - /// [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!); @@ -32,7 +26,6 @@ class BalRepository { } } - /// Gets a list of all [Bal] from remote only Future>> _getBalsNoCache() async { final result = await _apiClient.getBals(); switch (result) { @@ -44,16 +37,15 @@ class BalRepository { } } - /// Gets a [Bal] by [balId], either from cache or remote - Future> balById(int balId) async { + Future> balById(int id) async { if (_bals == null) { await getBals(); } - Bal? bal = _bals!.where((bal) => bal.id == balId).firstOrNull; + Bal? bal = _bals!.where((bal) => bal.id == id).firstOrNull; if (bal != null) { return Result.ok(bal); } - final result = await _apiClient.getBalById(balId); + final result = await _apiClient.getBalById(id); switch (result) { case Ok(): return Result.ok(result.value); @@ -62,13 +54,11 @@ 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(); @@ -76,14 +66,12 @@ 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( @@ -95,62 +83,52 @@ class BalRepository { return result; } - /// Changes a [Bal]'s [name], [startTime] or [endTime] Future> editBal( int id, String name, - DateTime startTime, - DateTime endTime, + DateTime start, + DateTime end, ) async { - final result = await _apiClient.editBal(id, name, startTime, endTime); + final result = await _apiClient.editBal(id, name, start, end); await _getBalsNoCache(); return result; } - /// 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); + Future> addBal(String name, DateTime start, DateTime end) async { + final result = await _apiClient.addBal(name, start, end); await _getBalsNoCache(); return result; } - /// Gets a [BalStats] from its [balId] - Future> getBalStats(int balId) async { - return _apiClient.getBalStats(balId); + Future> getBalStats(int id) async { + return _apiClient.getBalStats(id); } - /// Get [Accounting] of a [Bal] from remote only Future> getAccountingNoCache(int balId) async { final result = await _apiClient.getAccounting(balId); switch (result) { case Ok(): - _accountingMap[balId] = result.value; + accounting = result.value; break; default: } return result; } - /// Get [Accounting] of a [Bal] from cache or remote Future> getAccounting(int balId) async { - if (_accountingMap[balId] != null) { - return Result.ok(_accountingMap[balId]!); + if (accounting != null) { + return Result.ok(accounting!); } final result = await _apiClient.getAccounting(balId); switch (result) { case Ok(): - _accountingMap[balId] = result.value; + accounting = result.value; break; default: } return result; } - /// Manages what returning (of type [ReturnType]) does to cache and notifies remote Future> returnToId( int balId, int ownerId, @@ -161,32 +139,26 @@ class BalRepository { case Ok(): switch (type) { case ReturnType.books: - final owner = _accountingMap[balId]?.owners + final owner = accounting?.owners .where((el) => el.ownerId == ownerId) .firstOrNull; if (owner?.owedMoney == 0) { - _accountingMap[balId]?.owners.removeWhere( - (el) => el.ownerId == ownerId, - ); + accounting?.owners.removeWhere((el) => el.ownerId == ownerId); } owner?.owed = []; owner?.owedInstances = []; break; case ReturnType.money: - final owner = _accountingMap[balId]?.owners + final owner = accounting?.owners .where((el) => el.ownerId == ownerId) .firstOrNull; if (owner?.owed == null || owner!.owed.isEmpty) { - _accountingMap[balId]?.owners.removeWhere( - (el) => el.ownerId == ownerId, - ); + accounting?.owners.removeWhere((el) => el.ownerId == ownerId); } owner?.owedMoney = 0; break; case ReturnType.all: - _accountingMap[balId]?.owners.removeWhere( - (el) => el.ownerId == ownerId, - ); + accounting?.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 7a60d19..4a07167 100644 --- a/lib/data/repositories/book_instance_repository.dart +++ b/lib/data/repositories/book_instance_repository.dart @@ -6,19 +6,16 @@ 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, @@ -27,17 +24,15 @@ class BookInstanceRepository { return await _apiClient.getBookInstanceBySearch(balId, title, author); } - /// Sends a new [BookInstance]'s [book], [owner], [bal] and [price] - Future> sendNewBookInstance( + Future> sendBook( Book book, Owner owner, Bal bal, double price, ) async { - return await _apiClient.sendNewBookInstance(book, owner, bal, price); + return await _apiClient.sendBook(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 5d38be1..984d85b 100644 --- a/lib/data/repositories/book_repository.dart +++ b/lib/data/repositories/book_repository.dart @@ -2,19 +2,16 @@ 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); } - /// Gets a [Book] by its [bookId] - Future> getBookById(int bookId) async { - return await _apiClient.getBookById(bookId); + Future> getBookById(int id) async { + return await _apiClient.getBookById(id); } } diff --git a/lib/data/repositories/owner_repository.dart b/lib/data/repositories/owner_repository.dart index 817bea0..5020123 100644 --- a/lib/data/repositories/owner_repository.dart +++ b/lib/data/repositories/owner_repository.dart @@ -5,7 +5,6 @@ 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, @@ -15,25 +14,18 @@ class OwnerRepository { final ApiClient _apiClient; final WebsocketClient _wsClient; - - /// [StreamSubscription] to the [Stream] for [_wsClient] late final StreamSubscription sub; - - /// [List] of owners, updated by [_wsClient] List? _cachedOwners; + Owner? _sectionOwner; - /// [Owner] of the current user - Owner? _ownerOfUser; - - /// [Owner] of the current user - Future> get ownerOfUser async { - if (_ownerOfUser != null) { - return Result.ok(_ownerOfUser!); + Future> get sectionOwner async { + if (_sectionOwner != null) { + return Result.ok(_sectionOwner!); } - final result = await _apiClient.getOwnerOfUser(); + final result = await _apiClient.getSectionOwner(); switch (result) { case Ok(): - _ownerOfUser = result.value; + _sectionOwner = result.value; break; default: break; @@ -41,17 +33,16 @@ class OwnerRepository { return result; } - /// Gets an [Owner] from its [ownerId] - Future> getOwnerById(int ownerId) async { + Future> getOwnerById(int id) async { if (_cachedOwners != null) { final result1 = _cachedOwners! - .where((owner) => owner.id == ownerId) + .where((owner) => owner.id == id) .firstOrNull; if (result1 != null) { return Result.ok(result1); } } - return await _apiClient.getOwnerById(ownerId); + return await _apiClient.getOwnerById(id); } /// 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 c638d4c..383f269 100644 --- a/lib/data/services/api_client.dart +++ b/lib/data/services/api_client.dart @@ -1,17 +1,19 @@ import 'dart:convert'; +import 'dart:math'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart'; -import 'package:logger/logger.dart'; import 'package:seshat/config/constants.dart'; import 'package:seshat/domain/models/accounting.dart'; 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,33 +22,22 @@ extension StringExtension on String { } } -/// API Client to manage all authenticated REST routes +typedef AuthHeaderProvider = String? Function(); + class ApiClient { - ApiClient(); + ApiClient({String? host, int? port}); - /// JWT for registration + late final Command0 load; String? token; - - /// Readiness of the API Client bool isReady = false; - - /// Storage to access JWT FlutterSecureStorage? _secureStorage; - Logger log = Logger( - printer: PrettyPrinter( - colors: true, - lineLength: 100, - methodCount: 0, - dateTimeFormat: DateTimeFormat.dateAndTime, - ), - ); - /// Initializes connection to the [_secureStorage] Future _initStore() async { - _secureStorage ??= const FlutterSecureStorage(); + _secureStorage ??= const FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); } - /// Generates authorization headers and option [additionalHeaders] Future> _getHeaders([ Map? additionalHeaders, ]) async { @@ -62,62 +53,47 @@ 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)"); final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Accounting.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No BAL with this is exists in database"; - default: - throw "Unknown error of code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/bal/$balId/accounting"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Accounting.fromJSON(json)); + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// 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()}"; - log.i("Fetching: returnToId ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); final body = jsonEncode({"return_type": type.capitalize()}); + debugPrint(body); final response = await client.post( - Uri.parse(url), + Uri.parse( + "https://$apiBasePath/bal/${balId.toString()}/accounting/return/${ownerId.toString()}", + ), headers: headers, body: body, ); - switch (response.statusCode) { - case 200: - return Result.ok(response); - case 403: - throw "You don't own the specified BAL or owner"; - case 404: - throw "No BAL or owner with this id exists in database"; - case 409: - throw "Books and money have already been returned, or there was nothing to return"; - default: - throw "Unknown error of code ${response.statusCode.toString()}"; + if (response.statusCode == 200) { + return Result.ok(response); + } else { + throw "Unknown error ${response.statusCode.toString()}"; } } catch (e) { - log.e(e.toString()); + debugPrint(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -130,233 +106,200 @@ class ApiClient { * ================= */ - /// 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)"); + Future> getBalStats(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(BalStats.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No BAL with this id exists in the database"; - case 409: - throw "The specified BAL is not ended yet"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/bal/${id.toString()}/stats"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(BalStats.fromJSON(json)); + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// 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)"); + Future> stopBal(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.post(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No BAL with specified ID found"; - case 409: - throw "Selected BAL was not on ongoing state"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.post( + Uri.parse("https://$apiBasePath/bal/${id.toString()}/stop"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Bal.fromJSON(json)); + } else if (response.statusCode == 403) { + throw "You don't own the specified BAL"; + } else if (response.statusCode == 404) { + throw "No BAL with specified ID found"; + } else if (response.statusCode == 409) { + throw "Selected BAL was not on ongoing state"; + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// 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)"); + Future> startBal(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.post(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No BAL with specified ID found"; - case 409: - throw "Cannot have multiple BAl ongoing at the same time!"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.post( + Uri.parse("https://$apiBasePath/bal/${id.toString()}/start"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Bal.fromJSON(json)); + } else if (response.statusCode == 403) { + throw "You don't own the specified BAL"; + } else if (response.statusCode == 404) { + throw "No BAL with specified ID found"; + } else if (response.statusCode == 409) { + throw "Cannot have multiple BAl ongoing at the same time!"; + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// Changes the information about a [Bal], such as its [name], [startTime] or [endTime] Future> editBal( - int balId, + int id, String name, - DateTime startTime, - DateTime endTime, + DateTime start, + DateTime end, ) async { - 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": (startTime.millisecondsSinceEpoch / 1000).round(), - "end_timestamp": (endTime.millisecondsSinceEpoch / 1000).round(), + "start_timestamp": (start.millisecondsSinceEpoch / 1000).round(), + "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), }; final response = await client.patch( - Uri.parse(url), + Uri.parse("https://$apiBasePath/bal/${id.toString()}"), headers: headers, body: jsonEncode(body), ); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No bal with specified id"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Bal.fromJSON(json)); + } else { + throw Exception("Something went wrong"); } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// Gets a [Bal] from it's [balId] - Future> getBalById(int balId) async { - final url = "https://$apiBasePath/bal/${balId.toString()}"; - log.i("Fetching: getBalById ($url)"); + Future> getBalById(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - case 403: - throw "You don't own the specified BAL"; - case 404: - throw "No BAL with this id found in database"; - default: - throw "Unknown error"; + final response = await client.get( + Uri.parse("https://$apiBasePath/bal/${id.toString()}"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Bal.fromJSON(json)); + } else if (response.statusCode == 403) { + throw Exception("You don't own the specified bal"); + } else { + return Result.error( + Exception("No bal wirth this id exists the database"), + ); } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// 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)"); + Future> addBal(String name, DateTime start, DateTime end) async { final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); final body = { "name": name, - "start_timestamp": (startTime.millisecondsSinceEpoch / 1000).round(), - "end_timestamp": (endTime.millisecondsSinceEpoch / 1000).round(), + "start_timestamp": (start.millisecondsSinceEpoch / 1000).round(), + "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), }; final response = await client.post( - Uri.parse(url), + Uri.parse("https://$apiBasePath/bal"), headers: headers, body: jsonEncode(body), ); - switch (response.statusCode) { - case 201: - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - case 400: - throw "Time cannot go backwards"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 201) { + final json = jsonDecode(response.body); + return Result.ok(Bal.fromJSON(json)); + } else { + throw Exception("Something went wrong"); } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// Gets a [List] of all [Bal] Future>> getBals() async { - final url = "https://$apiBasePath/bals"; - log.i("Fetching: getBals ($url)"); final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); + final response = await client.get( + Uri.parse("https://$apiBasePath/bals"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as List; + debugPrint("\n\n\n\nRECEIVED : $json\n\n\n\n"); + debugPrint( + "\n\n\n\nFORMATTED : ${json.map((element) => Bal.fromJSON(element)).toList()}\n\n\n\n", + ); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body) as List; - return Result.ok( - json.map((element) => Bal.fromJSON(element)).toList(), - ); - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + return Result.ok(json.map((element) => Bal.fromJSON(element)).toList()); + } else { + throw Exception("Something wrong happened"); } } catch (e) { - log.e(e.toString()); + debugPrint("ERROR: ${e.toString()}"); return Result.error(Exception(e)); } finally { client.close(); } } - /// Gets the ongoing BAL for the user - Future> getOngoingBal() async { - final url = "https://$apiBasePath/bal/current"; - log.i("Fetching: getCurrentBal ($url)"); + Future> getCurrentBal() async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); + final response = await client.get( + Uri.parse("https://$apiBasePath/bal/current"), + headers: headers, + ); if (response.statusCode == 200) { final json = jsonDecode(response.body); return Result.ok(Bal.fromJSON(json)); @@ -378,50 +321,42 @@ class ApiClient { * =================== */ - /// Gets a [Book] by its [bookId] - Future> getBookById(int bookId) async { - final url = "https://$apiBasePath/book/id/${bookId.toString()}"; - log.i("Fetching: getBookById ($url)"); + Future> getBookById(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Book.fromJSON(json)); - case 404: - throw "No book with this id exists in database"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/book/id/${id.toString()}"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Book.fromJSON(json)); + } else { + throw Exception("The book was not found"); } } catch (e) { - log.e(e.toString()); return Result.error(Exception("API $e")); } finally { client.close(); } } - /// Gets a [Book] from its [ean] Future> getBookByEAN(String ean) async { - final url = "https://$apiBasePath/book/ean/$ean"; - log.i("Fetching: getBookByEan ($url)"); final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Book.fromJSON(json)); - case 404: - throw "No book with this EAN found in the database of BNF"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/book/ean/$ean"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Book.fromJSON(json)); + } else { + throw Exception("The book was not found"); } } catch (e) { - log.e(e.toString()); return Result.error(Exception("API $e")); } finally { client.close(); @@ -434,118 +369,93 @@ class ApiClient { * ============================= */ - /// Gets a [BookInstance] from it's [title], [author] or both Future>> getBookInstanceBySearch( int balId, String title, String author, ) async { - final url = "https://$apiBasePath/bal/${balId.toString()}/search"; - log.i("Fetching: getBookInstancesBySearch ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); final body = jsonEncode({"title": title, "author": author}); + debugPrint("\n\n\n\n$body\n\n\n\n"); final response = await client.post( - Uri.parse(url), + Uri.parse("https://$apiBasePath/bal/${balId.toString()}/search"), headers: headers, body: body, ); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body) as List; - return Result.ok( - json.map((el) => SearchResult.fromJSON(el)).toList(), - ); - case 403: - throw "You do not own the BAL"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as List; + debugPrint("\n\n\n\nJSON : $json\n\n\n\n"); + return Result.ok(json.map((el) => SearchResult.fromJSON(el)).toList()); + } else { + throw "Unknown Error"; } } catch (e) { - log.e(e.toString()); + debugPrint("\n\n\n\nERROR: ${e.toString()}\n\n\n\n"); return Result.error(Exception("API $e")); } finally { client.close(); } } - /// Gets a [BookInstance] from it's [ean] Future>> getBookInstanceByEAN( int balId, int ean, ) async { - final url = - "https://$apiBasePath/bal/${balId.toString()}/ean/${ean.toString()}/book_instances"; - log.i("Fetching: getBookInstancesByEan ($url)"); final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body) as List; - return Result.ok( - json.map((el) => BookInstance.fromJSON(el)).toList(), - ); - case 403: - throw "You do not own the BAL"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse( + "https://$apiBasePath/bal/${balId.toString()}/ean/${ean.toString()}/book_instances", + ), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as List; + return Result.ok(json.map((el) => BookInstance.fromJSON(el)).toList()); + } else { + throw "Unknown Error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception("API $e")); } finally { client.close(); } } - /// 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)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); + debugPrint("\n\n\n\nMAP: $books\n\n\n\n"); final body = jsonEncode(books); + debugPrint("\n\n\n\nSENT: $body\n\n\n\n"); final response = await client.post( - Uri.parse(url), + Uri.parse("https://$apiBasePath/book_instance/sell/bulk"), headers: headers, body: body, ); - switch (response.statusCode) { - case 200: - return Result.ok(response.statusCode); - case 403: - throw "You don't own one of the specified books"; - case 404: - throw "One of the books was not found"; - case 409: - throw "One of the books wasn't available"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 200) { + return Result.ok(response.statusCode); + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); + debugPrint("\n\n\n\nERROR : ${e.toString()}\n\n\n\n"); return Result.error(Exception(e)); } finally { client.close(); } } - /// Creates a new [BookInstance] from it's [book], it's [owner], it's [bal] and it's [price] - Future> sendNewBookInstance( + Future> sendBook( Book book, Owner owner, Bal bal, double price, ) async { - final url = "https://$apiBasePath/book_instance"; - log.i("Fetching: sendBook ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); @@ -556,18 +466,17 @@ class ApiClient { "price": price, }); final response = await client.post( - Uri.parse(url), + Uri.parse("https://$apiBasePath/book_instance"), headers: headers, body: body, ); - switch (response.statusCode) { - case 201: - final json = jsonDecode(response.body); - return Result.ok(BookInstance.fromJSON(json)); - case 403: - throw "You don't own that book instance"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 201) { + final json = jsonDecode(response.body); + return Result.ok(BookInstance.fromJSON(json)); + } else if (response.statusCode == 403) { + throw Exception("You don't own that book instance"); + } else { + throw Exception("Something wrong happened"); } } catch (e) { return Result.error(Exception(e)); @@ -582,72 +491,64 @@ class ApiClient { * ==================== */ - /// Gets an [Owner] by it's [ownerId] - Future> getOwnerById(int ownerId) async { - final url = "https://$apiBasePath/owner/${ownerId.toString()}"; - log.i("Fetching: getOwnerById ($url)"); + Future> getOwnerById(int id) async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Owner.fromJSON(json)); - case 403: - throw "You do not own specified owner"; - case 404: - throw "No owner with this id exists"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/owner/${id.toString()}"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Owner.fromJSON(json)); + } else { + throw Exception("The owner was not found"); } } catch (e) { - log.e(e.toString()); return Result.error(Exception("API $e")); } finally { client.close(); } } - /// Get the owner of the current user - Future> getOwnerOfUser() async { - final url = "https://$apiBasePath/owner/self"; - log.i("Fetching: getSectionOwner ($url)"); + Future> getSectionOwner() async { final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body); - return Result.ok(Owner.fromJSON(json)); - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/owner/self"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + return Result.ok(Owner.fromJSON(json)); + } else { + throw "Unknown error"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// Get a [List] of all [Owner] + /// Call on `/owners` to get a list of all [Owner]s Future>> getOwners() async { - final url = "https://$apiBasePath/owners"; - log.i("Fetching: getOwners ($url)"); final client = Client(); try { final headers = await _getHeaders(); - final response = await client.get(Uri.parse(url), headers: headers); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body) as List; - return Result.ok( - json.map((element) => Owner.fromJSON(element)).toList(), - ); - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/owners"), + headers: headers, + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as List; + return Result.ok( + json.map((element) => Owner.fromJSON(element)).toList(), + ); + } else { + throw Exception("Invalid request"); } } on Exception catch (error) { return Result.error(error); @@ -656,14 +557,12 @@ class ApiClient { } } - /// Adds an [Owner] from its [firstName], [lastName] and [contact] + /// Adds an owner to the database Future> addOwner( String firstName, String lastName, String contact, ) async { - final url = "https://$apiBasePath/owner"; - log.i("Fetching: addOwner ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); @@ -673,20 +572,18 @@ class ApiClient { "contact": contact, }; final response = await client.post( - Uri.parse(url), + Uri.parse("https://$apiBasePath/owner"), headers: headers, body: jsonEncode(body), ); - switch (response.statusCode) { - case 201: - final json = jsonDecode(response.body); - return Result.ok(Owner.fromJSON(json)); - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 201) { + final json = jsonDecode(response.body); + return Result.ok(Owner.fromJSON(json)); + } else { + throw Exception("Invalid request"); } - } catch (e) { - log.e(e.toString()); - return Result.error(Exception(e)); + } on Exception catch (error) { + return Result.error(error); } finally { client.close(); } diff --git a/lib/data/services/auth_client.dart b/lib/data/services/auth_client.dart index 3dc8496..4d77b4f 100644 --- a/lib/data/services/auth_client.dart +++ b/lib/data/services/auth_client.dart @@ -1,43 +1,29 @@ import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:logger/logger.dart'; import 'package:seshat/config/constants.dart'; import 'package:seshat/utils/result.dart'; -import "package:http/http.dart"; +import "package:http/http.dart" as http; -/// API Client to manage all unauthenticated REST routes class AuthClient { AuthClient(); - - /// Storage to access JWT FlutterSecureStorage? _secureStorage; - Logger log = Logger( - printer: PrettyPrinter( - colors: true, - lineLength: 100, - methodCount: 0, - dateTimeFormat: DateTimeFormat.dateAndTime, - ), - ); - /// Initializes connection to the [_secureStorage] Future _initStore() async { - _secureStorage ??= const FlutterSecureStorage(); + _secureStorage ??= const FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); } - /// Verifies the validity of the token stored in [_secureStorage] Future> hasValidToken() async { - final url = "https://$apiBasePath/token-check"; - log.i("Fetching: hasValidToken ($url)"); - final client = Client(); try { await _initStore(); bool hasToken = await _secureStorage!.containsKey(key: "token"); if (hasToken) { var token = await _secureStorage!.read(key: "token"); - var response = await client.post( - Uri.parse(url), + var url = Uri.parse("https://$apiBasePath/token-check"); + var response = await http.post( + url, headers: {"Content-Type": "application/json"}, body: jsonEncode({"token": token}), ); @@ -48,64 +34,52 @@ class AuthClient { } return Result.ok(false); } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); - } finally { - client.close(); } } - /// 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"); - var client = Client(); + var client = http.Client(); try { await _initStore(); + var url = Uri.parse("https://$apiBasePath/auth"); var response = await client.post( - Uri.parse(url), - headers: {"Content-Type": "application/json"}, + url, + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + }, body: jsonEncode({"password": password, "username": username}), ); - switch (response.statusCode) { - case 200: - var json = jsonDecode(response.body); - await _secureStorage!.write( - key: "token", - value: json["access_token"], - ); - return Result.ok(json["access_token"]); - case 401: - throw "Wrong credentials"; - case 500: - throw "Token creation error"; - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + if (response.statusCode == 200) { + var json = jsonDecode(response.body); + await _secureStorage!.write(key: "token", value: json["access_token"]); + return Result.ok(json["access_token"]); + } else if (response.statusCode == 401) { + return Result.error(Exception("Wrong credentials")); + } else { + return Result.error(Exception("Token creation error")); } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); } } - /// Gets the API version of the server Future> getRemoteApiVersion() async { - final url = "https://$apiBasePath/version"; - log.i("Fetching: getRemoteApiVersion ($url)"); - final client = Client(); + final client = http.Client(); try { - final response = await client.get(Uri.parse(url)); - switch (response.statusCode) { - case 200: - final json = jsonDecode(response.body) as int; - return Result.ok(json); - default: - throw "Unknown error with code ${response.statusCode.toString()}"; + final response = await client.get( + Uri.parse("https://$apiBasePath/version"), + ); + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as int; + return Result.ok(json); + } else { + throw "Something wrong happened"; } } catch (e) { - log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); diff --git a/lib/data/services/websocket_client.dart b/lib/data/services/websocket_client.dart index 1d85989..f8df18a 100644 --- a/lib/data/services/websocket_client.dart +++ b/lib/data/services/websocket_client.dart @@ -2,55 +2,33 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:logger/logger.dart'; import 'package:rxdart/rxdart.dart'; 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 { - /// Storage to access JWT - FlutterSecureStorage? _secureStorage; - - /// Raw channel of data from WebSocket WebSocketChannel? _channel; - - /// Global WebSocket Stream + FlutterSecureStorage? _secureStorage; 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( - colors: true, - lineLength: 100, - methodCount: 0, - dateTimeFormat: DateTimeFormat.dateAndTime, - ), - ); - /// Gets a stream of [Owner] Stream get owners => _ownersController.stream; - /// Initializes connection to the [_secureStorage] Future _initStore() async { - _secureStorage ??= const FlutterSecureStorage(); + _secureStorage ??= const FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); } - /// Connects to the websocket Future connect() async { - final url = "wss://$apiBasePath/ws"; - log.i("Webocket: $url"); await _initStore(); if (_channel != null) return; - _channel = WebSocketChannel.connect(Uri.parse(url)); + _channel = WebSocketChannel.connect(Uri.parse("wss://$apiBasePath/ws")); await _channel!.ready; var token = await _secureStorage!.read(key: "token"); @@ -82,13 +60,11 @@ 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 fd530ec..c330f51 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(), - selectedBalId: int.parse(state.pathParameters["id"] ?? ""), + id: int.parse(state.pathParameters["id"] ?? ""), ownerRepository: context.read(), ); return NoTransitionPage(child: BalPage(viewModel: viewModel)); @@ -72,6 +72,11 @@ GoRouter router(AuthRepository authRepository) => GoRouter( ); return NoTransitionPage(child: AddPage(viewModel: viewModel)); }, + // routes: [ + // GoRoute(path: Routes.addForm), + // GoRoute(path: Routes.addOwner), + // GoRoute(path: Routes.addPrice), + // ], ), GoRoute( path: Routes.sell, 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 3b0be3a..0e685ba 100644 --- a/lib/ui/add_page/view_model/add_view_model.dart +++ b/lib/ui/add_page/view_model/add_view_model.dart @@ -38,29 +38,18 @@ 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 of the current user - Owner? _ownerOfUser; + Owner? _sectionOwner; + Owner? get sectionOwner => _sectionOwner; - /// 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, @@ -96,11 +85,8 @@ class AddViewModel extends ChangeNotifier { * ================= */ - /// Ongoing [Bal] - Bal? _ongoingBal; - - /// Ongoing [Bal] - Bal? get ongoingBal => _ongoingBal; + Bal? _currentBal; + Bal? get currentBal => _currentBal; /* * =================== @@ -108,10 +94,7 @@ 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; @@ -124,25 +107,21 @@ class AddViewModel extends ChangeNotifier { * ================================= */ - /// Retrieves the book associated with an ean through a [barcode] - Future> scanBook(String ean) async { + /// Sends an api request with a [bacorde], then gets the [Book] that was + /// either created or retrieved. Sens the [Book] back wrapped in a [Result]. + Future> scanBook(BarcodeCapture barcode) async { + var ean = barcode.barcodes.first.rawValue!; var result = await _bookRepository.getBookByEAN(ean); return result; } - /// Creates a new Book Instance from its [book], [owner], [bal] and [price] - Future> sendNewBookInstance( + Future> sendBook( Book book, Owner owner, Bal bal, double price, ) async { - return await _bookInstanceRepository.sendNewBookInstance( - book, - owner, - bal, - price, - ); + return await _bookInstanceRepository.sendBook(book, owner, bal, price); } /* @@ -151,11 +130,9 @@ 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) { @@ -176,12 +153,11 @@ 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(): - _ongoingBal = result.value + _currentBal = result.value .where((bal) => bal.state == BalState.ongoing) .firstOrNull; break; @@ -192,7 +168,6 @@ class AddViewModel extends ChangeNotifier { return result; } - /// Loads all the necessary data about [Owner]s Future> _loadOwners() async { final result = await _ownerRepository.getOwners(); switch (result) { @@ -207,10 +182,10 @@ class AddViewModel extends ChangeNotifier { return result; } - final result2 = await _ownerRepository.ownerOfUser; + final result2 = await _ownerRepository.sectionOwner; switch (result2) { case Ok(): - _ownerOfUser = result2.value; + _sectionOwner = 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 e0e68ec..39808ac 100644 --- a/lib/ui/add_page/widgets/add_page.dart +++ b/lib/ui/add_page/widgets/add_page.dart @@ -22,27 +22,30 @@ class AddPage extends StatefulWidget { } class _AddPageState extends State { - final MobileScannerController scannerController = MobileScannerController( + num? price; + final MobileScannerController controller = MobileScannerController( formats: [BarcodeFormat.ean13], detectionTimeoutMs: 1000, ); @override void dispose() { - scannerController.dispose(); + controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); + // return Consumer( + // builder: (context, screen, child) { return Scaffold( bottomNavigationBar: AppNavigationBar(startIndex: 1), body: ListenableBuilder( listenable: widget.viewModel, builder: (context, child) => switch (widget.viewModel.isLoaded) { false => AwaitLoading(), - true => switch (widget.viewModel.ongoingBal) { + true => switch (widget.viewModel.currentBal) { null => Center( child: SizedBox( width: 300, @@ -75,37 +78,51 @@ class _AddPageState extends State { children: [ ColoredBox(color: Colors.black), MobileScanner( - controller: scannerController, + controller: controller, onDetect: (barcodes) async { - if (barcodes.barcodes.isEmpty) { - return; - } - if (widget.viewModel.currentOwner == null) { - _showMissingOwnerSnackBar( - context, - scannerController, - widget.viewModel, + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Attention : vous devez choisir un·e propriétaire", + ), + behavior: SnackBarBehavior.floating, + ), ); return; } - _scanEan( - context, - widget.viewModel, - barcodes.barcodes.first.rawValue!, - scannerController, + void setPrice(num newPrice) async { + setState(() { + price = newPrice; + }); + } + + Result result = await widget.viewModel.scanBook( + barcodes, ); + + switch (result) { + case Ok(): + await _confirmationDialogBuilder( + context, + setPrice, + controller, + widget.viewModel, + result.value, + ); + break; + case Error(): + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Erreur : ${result.error}"), + behavior: SnackBarBehavior.floating, + ), + ); + break; + } }, ), - - Center( - child: SvgPicture.asset( - 'assets/scan-overlay.svg', - height: (MediaQuery.sizeOf(context).height / 5) * 2, - ), - ), - SafeArea( child: SingleChildScrollView( child: Column( @@ -129,7 +146,7 @@ class _AddPageState extends State { ), onPressed: () => _ownerDialogBuilder( context, - scannerController, + controller, widget.viewModel, ), ), @@ -158,7 +175,7 @@ class _AddPageState extends State { ), ), ), - + Center(child: SvgPicture.asset('assets/scan-overlay.svg')), SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.end, @@ -170,22 +187,11 @@ class _AddPageState extends State { theme.cardColor, ), ), - onPressed: () { - if (widget.viewModel.currentOwner == null) { - _showMissingOwnerSnackBar( - context, - scannerController, - widget.viewModel, - ); - return; - } - - _formDialogBuilder( - context, - scannerController, - widget.viewModel, - ); - }, + onPressed: () => _formDialogBuilder( + context, + controller, + widget.viewModel, + ), child: Text("Enregistrer manuellement"), ), ), @@ -204,71 +210,18 @@ class _AddPageState extends State { } } -void _scanEan( - BuildContext context, - AddViewModel viewModel, - String ean, - MobileScannerController scannerController, { - Function(BuildContext)? leaveLastPopup, -}) async { - Result result = await viewModel.scanBook(ean); - - if (context.mounted) { - if (leaveLastPopup != null) { - leaveLastPopup(context); - } - switch (result) { - case Ok(): - await _confirmationDialogBuilder( - context, - scannerController, - viewModel, - result.value, - ); - break; - case Error(): - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Erreur : ${result.error}"), - behavior: SnackBarBehavior.floating, - ), - ); - break; - } - } -} - -void _showMissingOwnerSnackBar( - BuildContext context, - MobileScannerController scannerController, - AddViewModel viewModel, -) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Attention : vous devez choisir un·e propriétaire"), - duration: Duration(seconds: 4), - action: SnackBarAction( - label: "Choisir", - onPressed: () => - _ownerDialogBuilder(context, scannerController, viewModel), - ), - behavior: SnackBarBehavior.floating, - ), - ); -} - Future _confirmationDialogBuilder( BuildContext context, - MobileScannerController scannerController, + Function(num) setPrice, + MobileScannerController controller, AddViewModel viewModel, Book book, ) { - scannerController.stop(); + controller.stop(); - // Utility function to pass to downwards widgets void exitPopup(BuildContext localContext) { Navigator.of(localContext).pop(); - scannerController.start(); + controller.start(); } return showDialog( @@ -276,6 +229,7 @@ Future _confirmationDialogBuilder( barrierDismissible: false, builder: (context) => ConfirmationPopup( exitPopup: exitPopup, + setPrice: setPrice, viewModel: viewModel, book: book, ), @@ -289,7 +243,6 @@ Future _formDialogBuilder( ) { controller.stop(); - // Utility function to pass to downwards widgets void exitPopup(BuildContext localContext) { Navigator.of(localContext).pop(); controller.start(); @@ -298,12 +251,7 @@ Future _formDialogBuilder( return showDialog( context: context, barrierDismissible: false, - builder: (context) => FormPopup( - viewModel: viewModel, - exitPopup: exitPopup, - scannerController: controller, - scanEan: _scanEan, - ), + builder: (context) => FormPopup(viewModel: viewModel, exitPopup: exitPopup), ); } @@ -314,8 +262,7 @@ Future _ownerDialogBuilder( ) { controller.stop(); - // Utility function to pass to downwards widgets - void exitPopup(BuildContext localContext) { + void onPressAccept(BuildContext localContext) { Navigator.of(localContext).pop(); controller.start(); } @@ -324,6 +271,6 @@ Future _ownerDialogBuilder( context: context, barrierDismissible: false, builder: (context) => - OwnerPopup(viewModel: viewModel, exitPopup: exitPopup), + OwnerPopup(viewModel: viewModel, onPressAccept: onPressAccept), ); } diff --git a/lib/ui/add_page/widgets/confirmation_popup.dart b/lib/ui/add_page/widgets/confirmation_popup.dart index 998e3ea..f026ad6 100644 --- a/lib/ui/add_page/widgets/confirmation_popup.dart +++ b/lib/ui/add_page/widgets/confirmation_popup.dart @@ -7,11 +7,13 @@ class ConfirmationPopup extends StatefulWidget { const ConfirmationPopup({ super.key, required this.exitPopup, + required this.setPrice, required this.viewModel, required this.book, }); final Function(BuildContext) exitPopup; + final Function(num) setPrice; final AddViewModel viewModel; final Book book; @@ -22,11 +24,9 @@ class ConfirmationPopup extends StatefulWidget { class _ConfirmationPopupState extends State { final GlobalKey _formKey = GlobalKey(); double price = 0; - @override Widget build(BuildContext context) { final theme = Theme.of(context); - return AlertDialog( title: Text("Prix"), content: Form( @@ -72,7 +72,6 @@ class _ConfirmationPopupState extends State { ], ), ), - SizedBox(height: 10), (widget.viewModel.askPrice) ? TextFormField( @@ -81,28 +80,17 @@ class _ConfirmationPopupState extends State { border: OutlineInputBorder(), suffixText: "€", ), - keyboardType: TextInputType.numberWithOptions( - decimal: true, - signed: true, - ), + keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return "Indiquez un prix"; - } else if (double.tryParse( - value.replaceAll(",", "."), - ) == - null) { + } else if (num.tryParse(value) == null) { return "Le prix doit être un nombre"; - } else if (double.parse(value.replaceAll(",", ".")) < - 0) { - return "Le prix doit être positif ou nul"; } return null; }, onSaved: (newValue) { - price = double.parse( - newValue?.replaceAll(",", ".") ?? "0", - ); + price = double.parse(newValue!); }, ) : SizedBox(), @@ -112,7 +100,6 @@ class _ConfirmationPopupState extends State { ), actions: [ TextButton( - child: Text("Annuler"), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -122,21 +109,19 @@ class _ConfirmationPopupState extends State { ); widget.exitPopup(context); }, + child: Text("Annuler"), ), TextButton( - child: Text("Valider"), onPressed: () async { if (widget.viewModel.askPrice && _formKey.currentState!.validate()) { _formKey.currentState!.save(); - } else { - return; } - var result = await widget.viewModel.sendNewBookInstance( + var result = await widget.viewModel.sendBook( widget.book, widget.viewModel.currentOwner!, - widget.viewModel.ongoingBal!, + widget.viewModel.currentBal!, price, ); @@ -146,61 +131,49 @@ class _ConfirmationPopupState extends State { Navigator.of(context).pop(); showDialog( context: context, - barrierDismissible: false, - builder: (context) => - RegisteredBookPopup(widget: widget, price: price), + builder: (context) => AlertDialog( + title: Text( + "ID : ${widget.viewModel.currentOwner!.firstName[0].toUpperCase()}${widget.viewModel.currentOwner!.lastName[0].toUpperCase()}${(price == 0) ? "PL" : price.toString()}", + ), + content: Text( + (widget.viewModel.currentOwner!.id == + widget.viewModel.sectionOwner!.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 !", + ), + actions: [ + TextButton( + onPressed: () { + widget.exitPopup(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Enregistré: ${widget.book.title}", + ), + behavior: SnackBarBehavior.floating, + ), + ); + }, + child: Text("Ok"), + ), + ], + ), ); } break; case Error(): if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Erreur : ${result.error}")), + SnackBar( + content: Text( + "Une erreur est survenue : ${result.error}", + ), + ), ); } } }, - ), - ], - ); - } -} - -class RegisteredBookPopup extends StatelessWidget { - const RegisteredBookPopup({ - super.key, - required this.widget, - required this.price, - }); - - final ConfirmationPopup widget; - final double price; - - @override - Widget build(BuildContext context) { - return AlertDialog( - // This thing is the BookInstance's short ID - title: Text( - "ID : ${widget.viewModel.currentOwner!.firstName[0].toUpperCase()}${widget.viewModel.currentOwner!.lastName[0].toUpperCase()}${(price == 0) ? "PL" : price.toString()}", - ), - content: Text( - (widget.viewModel.currentOwner!.id == widget.viewModel.ownerOfUser!.id) - ? "Pensez à la gomette ! Ce livre appartient au syndicat." - : "Identifiant propriétaire de ce livre. Pensez à l'écrire pour retrouver lae propriétaire du livre lors de la vente ou du retour !", - ), - actions: [ - TextButton( - child: Text("Ok"), - onPressed: () { - widget.exitPopup(context); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Livre enregistré: ${widget.book.title}"), - behavior: SnackBarBehavior.floating, - duration: Duration(seconds: 2), - ), - ); - }, + child: Text("Valider"), ), ], ); diff --git a/lib/ui/add_page/widgets/form_popup.dart b/lib/ui/add_page/widgets/form_popup.dart index 47a3104..7f6af10 100644 --- a/lib/ui/add_page/widgets/form_popup.dart +++ b/lib/ui/add_page/widgets/form_popup.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:seshat/ui/add_page/view_model/add_view_model.dart'; class FormPopup extends StatelessWidget { @@ -7,14 +6,10 @@ class FormPopup extends StatelessWidget { super.key, required this.viewModel, required this.exitPopup, - required this.scannerController, - required this.scanEan, }); final AddViewModel viewModel; final Function(BuildContext) exitPopup; - final MobileScannerController scannerController; - final Function scanEan; @override Widget build(BuildContext context) { @@ -36,8 +31,6 @@ class FormPopup extends StatelessWidget { return _ManualEANPopup( exitPopup: exitPopup, viewModel: viewModel, - scannerController: scannerController, - scanEan: scanEan, ); }, ); @@ -58,7 +51,6 @@ class FormPopup extends StatelessWidget { ), ), ), - Card( clipBehavior: Clip.hardEdge, child: InkWell( @@ -101,24 +93,11 @@ class FormPopup extends StatelessWidget { } } -/* - * ====================== - * ====< MANUAL EAN >==== - * ====================== -*/ - class _ManualEANPopup extends StatefulWidget { - const _ManualEANPopup({ - required this.exitPopup, - required this.viewModel, - required this.scannerController, - required this.scanEan, - }); + const _ManualEANPopup({required this.exitPopup, required this.viewModel}); final Function(BuildContext) exitPopup; final AddViewModel viewModel; - final MobileScannerController scannerController; - final Function scanEan; @override State<_ManualEANPopup> createState() => _ManualEANPopupState(); @@ -127,11 +106,11 @@ class _ManualEANPopup extends StatefulWidget { class _ManualEANPopupState extends State<_ManualEANPopup> { final GlobalKey _formKey = GlobalKey(); String? ean; - + num? price; @override Widget build(BuildContext context) { return AlertDialog( - title: Text("Entrée manuelle par EAN"), + title: Text("Recherche par EAN"), content: Form( key: _formKey, child: Column( @@ -149,39 +128,57 @@ class _ManualEANPopupState extends State<_ManualEANPopup> { validator: (value) { if (value == null || value.length != 13 || - int.tryParse(value) == null || - int.parse(value) < 0) { + int.tryParse(value) == null) { return "L'entrée n'est pas un code EAN-13 valide"; } return null; }, ), + SizedBox(height: 10), + ListenableBuilder( + listenable: widget.viewModel, + builder: (context, child) { + return (widget.viewModel.askPrice) + ? TextFormField( + decoration: InputDecoration( + labelText: "Prix", + border: OutlineInputBorder(), + suffixText: "€", + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || value.isEmpty) { + return "Indiquez un prix"; + } else if (num.tryParse(value) == null) { + return "Le prix doit être un nombre"; + } + return null; + }, + onSaved: (newValue) { + price = num.parse(newValue!); + }, + ) + : SizedBox(); + }, + ), ], ), ), actions: [ TextButton( - child: Text("Annuler"), onPressed: () { widget.exitPopup(context); }, + child: Text("Annuler"), ), TextButton( - child: Text("Valider"), onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); - widget.scanEan( - context, - widget.viewModel, - ean!, - widget.scannerController, - leaveLastPopup: (context) { - Navigator.of(context).pop(); - }, - ); + widget.exitPopup(context); } }, + child: Text("Valider"), ), ], ); @@ -276,17 +273,12 @@ class _FullyManualState extends State<_FullyManual> { border: OutlineInputBorder(), suffixText: "€", ), - keyboardType: TextInputType.numberWithOptions( - decimal: true, - signed: true, - ), + keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return "Indiquez un prix"; } else if (num.tryParse(value) == null) { return "Le prix doit être un nombre"; - } else if (num.parse(value) < 0) { - return "Le prix doit être positif ou nul"; } return null; }, diff --git a/lib/ui/add_page/widgets/owner_popup.dart b/lib/ui/add_page/widgets/owner_popup.dart index 8627c13..35fe4b3 100644 --- a/lib/ui/add_page/widgets/owner_popup.dart +++ b/lib/ui/add_page/widgets/owner_popup.dart @@ -6,11 +6,11 @@ class OwnerPopup extends StatefulWidget { const OwnerPopup({ super.key, required this.viewModel, - required this.exitPopup, + required this.onPressAccept, }); final AddViewModel viewModel; - final Function(BuildContext) exitPopup; + final Function(BuildContext) onPressAccept; @override State createState() => _OwnerPopupState(); @@ -166,7 +166,7 @@ class _OwnerPopupState extends State { }); } } - widget.exitPopup(context); + widget.onPressAccept(context); }, child: Text( (!showNewOwner && searchController.text == "") diff --git a/lib/ui/auth/viewmodel/login_view_model.dart b/lib/ui/auth/viewmodel/login_view_model.dart index 51179f0..190ea46 100644 --- a/lib/ui/auth/viewmodel/login_view_model.dart +++ b/lib/ui/auth/viewmodel/login_view_model.dart @@ -13,10 +13,8 @@ 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); @@ -29,12 +27,10 @@ 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) { @@ -48,7 +44,6 @@ 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 c3cdbb9..f9947f1 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.selectedBalId, + required this.id, required OwnerRepository ownerRepository, }) : _balRepository = balRepository, _ownerRepository = ownerRepository { @@ -30,50 +30,31 @@ class BalViewModel extends ChangeNotifier { * ===================== */ - /// 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] + Bal? _bal; + int id; + Bal? get bal => _bal; bool isABalOngoing = false; - /// Stops a [Bal] - Future> stopBal(int balId) async { - isLoaded = false; - notifyListeners(); - final result = await _balRepository.stopBal(balId); + Future> stopBal(int id) async { + final result = await _balRepository.stopBal(id); switch (result) { case Ok(): - _selectedBal = result.value; + _bal = result.value; + notifyListeners(); break; default: } - final result2 = await _loadEnded(); - switch (result2) { - case Ok(): - isLoaded = true; - break; - case Error(): - break; - } - notifyListeners(); return result; } - /// Starts a [Bal] - Future> startBal(int balId) async { + Future> startBal(int id) async { if (isABalOngoing) { return Result.error(Exception("Cannot have multiple BALs ongoing !")); } - final result = await _balRepository.startBal(balId); + final result = await _balRepository.startBal(id); switch (result) { case Ok(): - _selectedBal = result.value; + _bal = result.value; notifyListeners(); break; default: @@ -81,20 +62,21 @@ class BalViewModel extends ChangeNotifier { return result; } - /// Edits a [Bal]'s [name], [startTime] or [endTime] Future> editBal( int id, String name, - DateTime startTime, - DateTime endTime, + DateTime start, + DateTime end, ) async { - final result = await _balRepository.editBal(id, name, startTime, endTime); + final result = await _balRepository.editBal(id, name, start, end); switch (result) { case Ok(): - _selectedBal = result.value; + debugPrint("\n\n\n\nDID EDIT\n\n\n\n"); + _bal = result.value; notifyListeners(); break; case Error(): + debugPrint("\n\n\n\nERROR: ${result.error}"); break; } return result; @@ -107,16 +89,11 @@ class BalViewModel extends ChangeNotifier { */ // Specific to ended state - - /// Owners a book or money is owed to List? owedToOwners; - - /// Statistics about the [_selectedBal] + double? totalOwed; BalStats? stats; - /// 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( + Future applyAccountingOwners( List owners, Map books, ) async { @@ -137,20 +114,15 @@ 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( - selectedBalId, - ownerId, - type, - ); - final result2 = await _balRepository.getAccounting(selectedBalId); + final result = await _balRepository.returnToId(id, ownerId, type); + final result2 = await _balRepository.getAccounting(id); switch (result2) { case Ok(): - _updateOwedToOwnersWithBooks(result2.value.owners, result2.value.books); + applyAccountingOwners(result2.value.owners, result2.value.books); break; case Error(): - break; + debugPrint(result2.error.toString()); } notifyListeners(); return result; @@ -162,31 +134,31 @@ 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 = - (_selectedBal == null || _selectedBal?.state != BalState.ended) + isLoaded = (_bal == null || _bal?.state != BalState.ended) ? true : false; break; default: break; } - if (_selectedBal?.state == BalState.ended) { + debugPrint("$isLoaded"); + if (_bal?.state == BalState.ended) { final result2 = await _loadEnded(); + debugPrint("Hello"); switch (result2) { case Ok(): isLoaded = true; break; case Error(): + debugPrint("No ${result2.error}"); break; } } @@ -194,12 +166,11 @@ class BalViewModel extends ChangeNotifier { return result1; } - /// Loads all common [Bal] information Future> _loadBal() async { - final result = await _balRepository.balById(selectedBalId); + final result = await _balRepository.balById(id); switch (result) { case Ok(): - _selectedBal = result.value; + _bal = result.value; break; case Error(): break; @@ -208,16 +179,15 @@ class BalViewModel extends ChangeNotifier { return result; } - /// Loads [Bal] information when it is [BalState.ended] Future> _loadEnded() async { - final result = await _balRepository.getAccountingNoCache(selectedBalId); + final result = await _balRepository.getAccountingNoCache(id); switch (result) { case Ok(): - _updateOwedToOwnersWithBooks(result.value.owners, result.value.books); + applyAccountingOwners(result.value.owners, result.value.books); break; default: } - final result2 = await _balRepository.getBalStats(selectedBalId); + final result2 = await _balRepository.getBalStats(id); 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 e7e3467..2347dc3 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.selectedBal == null) { + true => switch (widget.viewModel.bal == 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.selectedBal!.state) { + false => switch (widget.viewModel.bal!.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 2b62688..70a9905 100644 --- a/lib/ui/bal_page/widget/ended/bal_ended_screen.dart +++ b/lib/ui/bal_page/widget/ended/bal_ended_screen.dart @@ -30,15 +30,12 @@ class _BalEndedScreenState extends State return Scaffold( bottomNavigationBar: AppNavigationBar(startIndex: 0), appBar: AppBar( - title: Text(widget.viewModel.selectedBal!.name), + title: Text(widget.viewModel.bal!.name), bottom: TabBar( controller: tabController, tabs: [ Tab(text: "Statistiques"), - Tab( - text: - "À rendre (${widget.viewModel.owedToOwners?.length.toString() ?? "0"})", - ), + Tab(text: "À rendre"), ], ), ), 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 db9fd01..a9ca41e 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.selectedBal!.name)), + appBar: AppBar(title: Text(viewModel.bal!.name)), body: Padding( padding: const EdgeInsets.all(10.0), child: Column( @@ -38,9 +38,7 @@ class BalOngoingScreen extends StatelessWidget { ), TextButton( onPressed: () async { - await viewModel.stopBal( - viewModel.selectedBal!.id, - ); + await viewModel.stopBal(viewModel.bal!.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 e5d7164..2e57c22 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.selectedBal!.name), + title: Text(viewModel.bal!.name), actions: [ IconButton( onPressed: () { @@ -57,9 +57,7 @@ class BalPendingScreen extends StatelessWidget { ), TextButton( onPressed: () async { - await viewModel.startBal( - viewModel.selectedBal!.id, - ); + await viewModel.startBal(viewModel.bal!.id); if (context.mounted) { Navigator.of(context).pop(); } @@ -98,8 +96,8 @@ class _EditPopup extends State { firstDate: DateTime(DateTime.now().year - 1), lastDate: DateTime(DateTime.now().year + 2), initialDateRange: DateTimeRange( - start: start ?? widget.viewModel.selectedBal!.startTime, - end: end ?? widget.viewModel.selectedBal!.endTime, + start: start ?? widget.viewModel.bal!.startTime, + end: end ?? widget.viewModel.bal!.endTime, ), ); @@ -128,7 +126,7 @@ class _EditPopup extends State { labelText: "Nom de la BAL", border: OutlineInputBorder(), ), - initialValue: widget.viewModel.selectedBal!.name, + initialValue: widget.viewModel.bal!.name, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer un nom"; @@ -171,7 +169,7 @@ class _EditPopup extends State { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); - final Bal bal = widget.viewModel.selectedBal!; + final Bal bal = widget.viewModel.bal!; 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 e6b68d2..29ffeda 100644 --- a/lib/ui/home_page/view_model/home_view_model.dart +++ b/lib/ui/home_page/view_model/home_view_model.dart @@ -18,25 +18,18 @@ class HomeViewModel extends ChangeNotifier { * ================= */ - /// [List] of all [Bal] List _bals = []; - - /// [List] of all [Bal] List get bals => _bals; - /// [Bal] currently [BalState.ongoing] - Bal? _ongoingBal; + Bal? _currentBal; + Bal? get currentBal => _currentBal; - /// [Bal] currently [BalState.ongoing] - Bal? get ongoingBal => _ongoingBal; - - /// Creates a [Bal] from its [name], [startTime] and [endTime] Future> createBal( String name, - DateTime startTime, - DateTime endTime, + DateTime start, + DateTime end, ) async { - final result = await _balRepository.addBal(name, startTime, endTime); + final result = await _balRepository.addBal(name, start, end); switch (result) { case Ok(): final result2 = await _balRepository.getBals(); @@ -61,11 +54,9 @@ 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) { @@ -79,13 +70,12 @@ 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)); - _ongoingBal = _bals + _currentBal = _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 2f56dbd..adc6684 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.ongoingBal?.id, + (el) => el.id != widget.viewModel.currentBal?.id, )) Padding( padding: const EdgeInsets.symmetric( @@ -81,20 +81,20 @@ class _HomePageState extends State { ], ), ), - switch (widget.viewModel.ongoingBal == null) { + switch (widget.viewModel.currentBal == 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.ongoingBal!.name), + title: Text(widget.viewModel.currentBal!.name), subtitle: Text("BAL en cours"), trailing: IconButton( onPressed: () { _moveToBal( context, - widget.viewModel.ongoingBal!.id, + widget.viewModel.currentBal!.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 77b0393..549b328 100644 --- a/lib/ui/sell_page/view_model/sell_view_model.dart +++ b/lib/ui/sell_page/view_model/sell_view_model.dart @@ -31,13 +31,10 @@ class SellViewModel extends ChangeNotifier { final BookRepository _bookRepository; final OwnerRepository _ownerRepository; - /// 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; + bool _showScan = false; + bool get showScan => _showScan; + set showScan(bool newValue) { + _showScan = newValue; notifyListeners(); } @@ -47,66 +44,56 @@ class SellViewModel extends ChangeNotifier { * =============================== */ - /// Books in the sell - final List _booksInSell = []; + final List _soldBooks = []; + List get soldBooks => _soldBooks; - /// 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 minimumAmountToPay = 0; + double minimumAmount = 0; - /// Adds a book to the [_booksInSell] - void addBookToSell(BookStack bookToAdd) { - minimumAmountToPay += bookToAdd.instance.price; - _booksInSell.add(bookToAdd); + void sellBook(BookStack addedBook) { + minimumAmount += addedBook.instance.price; + _soldBooks.add(addedBook); notifyListeners(); } - /// Sends the sell - void sendSell(double givenMoney) async { + void sendSell(double givenAmount) async { isSendingSell = true; notifyListeners(); - List booksToSend = []; - int numberOfPL = 0; - for (BookStack book in _booksInSell) { + List toSend = []; + int nbOfPl = 0; + for (BookStack book in _soldBooks) { if (book.instance.price != 0) { book.instance.soldPrice = book.instance.price; - givenMoney -= book.instance.price; - booksToSend.add(book.instance); + givenAmount -= book.instance.price; + toSend.add(book.instance); } else { - numberOfPL++; + nbOfPl++; } } - if (numberOfPL != 0) { - double moneyPerPL = givenMoney / numberOfPL; - for (BookStack book in _booksInSell) { + if (nbOfPl != 0) { + double amountPerPl = givenAmount / nbOfPl; + for (BookStack book in _soldBooks) { if (book.instance.price == 0) { - book.instance.soldPrice = moneyPerPL; - booksToSend.add(book.instance); + book.instance.soldPrice = amountPerPl; + toSend.add(book.instance); } } } - await _bookInstanceRepository.sellBooks(booksToSend); - _booksInSell.clear(); + await _bookInstanceRepository.sellBooks(toSend); + _soldBooks.clear(); isSendingSell = false; notifyListeners(); } - /// Removes a book from the sell - void removeBookFromSell(int bookId) { - _booksInSell.removeWhere((book) => book.instance.id == bookId); + void deleteBook(int id) { + _soldBooks.removeWhere((book) => book.instance.id == id); notifyListeners(); } - /// Search a book by [title] or [author] Future searchBook(String title, String author) async { Bal? bal = await _balRepository.ongoingBal(); isScanLoaded = false; @@ -119,21 +106,15 @@ 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; } - - // In case the instance is already in the sell - if (_booksInSell + if (_soldBooks .where((book) => book.instance.id == searchResult.instance.id) .isNotEmpty) { continue; } - - // Search for the owner Owner owner; final result2 = await _ownerRepository.getOwnerById( searchResult.instance.ownerId, @@ -145,7 +126,6 @@ class SellViewModel extends ChangeNotifier { case Error(): continue; } - _scannedBooks.add( BookStack(searchResult.book, searchResult.instance, owner), ); @@ -160,19 +140,18 @@ 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? ongoingBal = await _balRepository.ongoingBal(); + Bal? bal = await _balRepository.ongoingBal(); _scannedBooks.clear(); - final result1 = await _bookInstanceRepository.getByEan(ongoingBal!.id, ean); - switch (result1) { + final result = await _bookInstanceRepository.getByEan(bal!.id, ean); + switch (result) { case Ok(): Book book; final result2 = await _bookRepository.getBookById( - result1.value.first.bookId, + result.value.first.bookId, ); switch (result2) { case Ok(): @@ -181,22 +160,15 @@ class SellViewModel extends ChangeNotifier { case Error(): return; } - - // 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 + for (BookInstance instance in result.value) { if (instance.available == false) { continue; } - - // In case the instance is already in the sell - if (_booksInSell + if (_soldBooks .where((book) => book.instance.id == instance.id) .isNotEmpty) { continue; } - - // Search for the owner Owner owner; final result3 = await _ownerRepository.getOwnerById(instance.ownerId); switch (result3) { @@ -206,7 +178,6 @@ class SellViewModel extends ChangeNotifier { case Error(): continue; } - _scannedBooks.add(BookStack(book, instance, owner)); } break; @@ -225,11 +196,8 @@ class SellViewModel extends ChangeNotifier { * ================= */ - /// The currently ongoing [Bal] - Bal? _ongoingBal; - - /// The currently ongoing [Bal] - get ongoingBal => _ongoingBal; + Bal? _currentBal; + get currentBal => _currentBal; /* * ================================= @@ -237,11 +205,9 @@ 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) { @@ -255,12 +221,11 @@ class SellViewModel extends ChangeNotifier { return result1; } - /// Loads information about [Bal] Future> _loadBal() async { final result = await _balRepository.getBals(); switch (result) { case Ok(): - _ongoingBal = result.value + _currentBal = 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 3036fb8..1b15ee8 100644 --- a/lib/ui/sell_page/widgets/scan_screen.dart +++ b/lib/ui/sell_page/widgets/scan_screen.dart @@ -41,9 +41,6 @@ class _ScanScreenState extends State { MobileScanner( controller: controller, onDetect: (barcodes) async { - if (barcodes.barcodes.isEmpty) { - return; - } controller.stop(); showDialog( context: context, @@ -54,24 +51,19 @@ class _ScanScreenState extends State { await widget.viewModel.scanBook(barcodes); }, ), - Center( - child: SvgPicture.asset( - 'assets/scan-overlay.svg', - height: (MediaQuery.sizeOf(context).height / 5) * 2, - ), - ), SafeArea( child: Column( children: [ IconButton( onPressed: () { - widget.viewModel.showScanScreen = false; + widget.viewModel.showScan = false; }, icon: Icon(Icons.arrow_back), ), ], ), ), + Center(child: SvgPicture.asset('assets/scan-overlay.svg')), Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/ui/sell_page/widgets/sell_choice_popup.dart b/lib/ui/sell_page/widgets/sell_choice_popup.dart index f96bb93..1931a86 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.addBookToSell(book); + viewModel.sellBook(book); Navigator.of(context).pop(); - viewModel.showScanScreen = false; + viewModel.showScan = 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 784ff23..46b2938 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.ongoingBal) { + true => switch (widget.viewModel.currentBal) { 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.booksInSell) + in widget.viewModel.soldBooks) Padding( padding: const EdgeInsets.symmetric( horizontal: 15, @@ -99,10 +99,9 @@ class _SellPageState extends State { ), trailing: IconButton( onPressed: () { - widget.viewModel - .removeBookFromSell( - book.instance.id, - ); + widget.viewModel.deleteBook( + book.instance.id, + ); }, icon: Icon(Icons.delete), ), @@ -114,7 +113,7 @@ class _SellPageState extends State { ), SizedBox(height: 40), Text( - "Montant minimum à payer : ${widget.viewModel.minimumAmountToPay.toString()}€", + "Montant minimum à payer : ${widget.viewModel.minimumAmount.toString()}€", ), Padding( padding: const EdgeInsets.symmetric(horizontal: 60.0), @@ -123,15 +122,14 @@ class _SellPageState extends State { controller: price, decoration: InputDecoration( labelText: "Argent reçu", + hintText: + "Utilisez un point (.) pour les virgules (,)", helperText: "L'argent reçu sera réparti automatiquement.", suffixText: "€", border: OutlineInputBorder(), ), - keyboardType: TextInputType.numberWithOptions( - decimal: true, - signed: true, - ), + keyboardType: TextInputType.number, ), ), ), @@ -141,21 +139,7 @@ class _SellPageState extends State { children: [ IconButton( onPressed: () { - if (widget.viewModel.scannedBooks.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "La vente doit comporter au moins un livre", - ), - behavior: SnackBarBehavior.floating, - ), - ); - return; - } - if (double.tryParse( - price.text.replaceFirst(",", "."), - ) == - null) { + if (double.tryParse(price.text) == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -165,10 +149,8 @@ class _SellPageState extends State { ), ); return; - } else if (double.parse( - price.text.replaceFirst(",", "."), - ) < - widget.viewModel.minimumAmountToPay) { + } else if (double.parse(price.text) < + widget.viewModel.minimumAmount) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -180,9 +162,7 @@ class _SellPageState extends State { return; } widget.viewModel.sendSell( - double.parse( - price.text.replaceFirst(",", "."), - ), + double.parse(price.text), ); ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -203,7 +183,7 @@ class _SellPageState extends State { SizedBox(width: 70), IconButton( onPressed: () { - widget.viewModel.showScanScreen = true; + widget.viewModel.showScan = true; }, icon: Icon(Icons.add), style: ButtonStyle( @@ -217,7 +197,7 @@ class _SellPageState extends State { ], ), ), - (widget.viewModel.showScanScreen) + (widget.viewModel.showScan) ? ScanScreen(viewModel: widget.viewModel) : SizedBox(), ], diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index aecce92..c44ff01 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,12 @@ import FlutterMacOS import Foundation -import flutter_secure_storage_darwin +import flutter_secure_storage_macos import mobile_scanner import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 39a3563..f140cfe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -151,50 +151,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: f7eceb0bc6f4fd0441e29d43cab9ac2a1c5ffd7ea7b64075136b718c46954874 + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" url: "https://pub.dev" source: hosted - version: "10.0.0-beta.4" - flutter_secure_storage_darwin: - dependency: transitive - description: - name: flutter_secure_storage_darwin - sha256: f226f2a572bed96bc6542198ebaec227150786e34311d455a7e2d3d06d951845 - url: "https://pub.dev" - source: hosted - version: "0.1.0" + version: "9.2.4" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c" + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "1.1.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21 + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.1.2" flutter_svg: dependency: "direct main" description: @@ -253,6 +253,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" json_annotation: dependency: transitive description: @@ -265,26 +273,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -293,16 +301,8 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" - logger: - dependency: "direct main" - description: - name: logger - sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" - url: "https://pub.dev" - source: hosted - version: "2.6.1" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 @@ -510,10 +510,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" typed_data: dependency: transitive description: @@ -550,10 +550,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fc17ee5..0ffed5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,16 +41,15 @@ dependencies: provider: ^6.1.5 flutter_svg: ^2.2.0 go_router: ^16.0.0 + logging: ^1.3.0 http: ^1.4.0 web_socket_channel: ^3.0.3 nested: ^1.0.0 - #flutter_secure_storage: ^9.2.4 - flutter_secure_storage: ^10.0.0-beta.4 + flutter_secure_storage: ^9.2.4 rxdart: ^0.28.0 intl: ^0.20.2 flutter_launcher_icons: ^0.14.4 fl_chart: ^1.0.0 - logger: ^2.6.1 dev_dependencies: flutter_test: diff --git a/scripts/build-web.sh b/scripts/build-web.sh deleted file mode 100755 index c7456c3..0000000 --- a/scripts/build-web.sh +++ /dev/null @@ -1 +0,0 @@ -flutter build web --release --wasm --base-href /app/