diff --git a/lib/data/services/api_client.dart b/lib/data/services/api_client.dart index 383f269..2d9e273 100644 --- a/lib/data/services/api_client.dart +++ b/lib/data/services/api_client.dart @@ -1,10 +1,10 @@ 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'; @@ -22,8 +22,6 @@ extension StringExtension on String { } } -typedef AuthHeaderProvider = String? Function(); - class ApiClient { ApiClient({String? host, int? port}); @@ -31,11 +29,17 @@ class ApiClient { String? token; bool isReady = false; FlutterSecureStorage? _secureStorage; + Logger log = Logger( + printer: PrettyPrinter( + colors: true, + lineLength: 100, + methodCount: 0, + dateTimeFormat: DateTimeFormat.dateAndTime, + ), + ); Future _initStore() async { - _secureStorage ??= const FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ); + _secureStorage ??= const FlutterSecureStorage(aOptions: AndroidOptions()); } Future> _getHeaders([ @@ -54,20 +58,25 @@ class ApiClient { */ 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("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"; + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -75,25 +84,32 @@ class ApiClient { } 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( - "https://$apiBasePath/bal/${balId.toString()}/accounting/return/${ownerId.toString()}", - ), + Uri.parse(url), headers: headers, body: body, ); - if (response.statusCode == 200) { - return Result.ok(response); - } else { - throw "Unknown error ${response.statusCode.toString()}"; + 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()}"; } } catch (e) { - debugPrint(e.toString()); + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -107,20 +123,27 @@ class ApiClient { */ Future> getBalStats(int id) async { + final url = "https://$apiBasePath/bal/${id.toString()}/stats"; + log.i("Fetching: getBalStats ($url)"); final client = Client(); try { final headers = await _getHeaders(); - 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"; + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -128,26 +151,27 @@ class ApiClient { } Future> stopBal(int id) async { + final url = "https://$apiBasePath/bal/${id.toString()}/stop"; + log.i("Fetching: stopBal ($url)"); final client = Client(); try { final headers = await _getHeaders(); - 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"; + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -155,26 +179,27 @@ class ApiClient { } Future> startBal(int id) async { + final url = "https://$apiBasePath/bal/${id.toString()}/start"; + log.i("Fetching: startBal ($url)"); final client = Client(); try { final headers = await _getHeaders(); - 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"; + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -187,6 +212,8 @@ class ApiClient { DateTime start, DateTime end, ) async { + final url = "https://$apiBasePath/bal/${id.toString()}"; + log.i("Fetching: editBal ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); @@ -196,17 +223,23 @@ class ApiClient { "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), }; final response = await client.patch( - Uri.parse("https://$apiBasePath/bal/${id.toString()}"), + Uri.parse(url), headers: headers, body: jsonEncode(body), ); - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - } else { - throw Exception("Something went wrong"); + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -214,24 +247,25 @@ class ApiClient { } Future> getBalById(int id) async { + final url = "https://$apiBasePath/bal/${id.toString()}"; + log.i("Fetching: getBalById ($url)"); final client = Client(); try { final headers = await _getHeaders(); - 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"), - ); + 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"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -239,6 +273,8 @@ class ApiClient { } Future> addBal(String name, DateTime start, DateTime end) async { + final url = "https://$apiBasePath/bal"; + log.i("Fetching: addBal ($url)"); final client = Client(); try { final headers = await _getHeaders({"Content-Type": "application/json"}); @@ -248,17 +284,21 @@ class ApiClient { "end_timestamp": (end.millisecondsSinceEpoch / 1000).round(), }; final response = await client.post( - Uri.parse("https://$apiBasePath/bal"), + Uri.parse(url), headers: headers, body: jsonEncode(body), ); - if (response.statusCode == 201) { - final json = jsonDecode(response.body); - return Result.ok(Bal.fromJSON(json)); - } else { - throw Exception("Something went wrong"); + 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()}"; } } catch (e) { + log.e(e.toString()); return Result.error(Exception(e)); } finally { client.close(); @@ -266,20 +306,14 @@ class ApiClient { } 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("https://$apiBasePath/bals"), - headers: headers, - ); + final response = await client.get(Uri.parse(url), 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", - ); - return Result.ok(json.map((element) => Bal.fromJSON(element)).toList()); } else { throw Exception("Something wrong happened"); diff --git a/lib/ui/add_page/widgets/confirmation_popup.dart b/lib/ui/add_page/widgets/confirmation_popup.dart index f026ad6..6a6ca74 100644 --- a/lib/ui/add_page/widgets/confirmation_popup.dart +++ b/lib/ui/add_page/widgets/confirmation_popup.dart @@ -1,3 +1,5 @@ +import 'dart:nativewrappers/_internal/vm/lib/ffi_patch.dart'; + import 'package:flutter/material.dart'; import 'package:seshat/domain/models/book.dart'; import 'package:seshat/ui/add_page/view_model/add_view_model.dart'; @@ -80,12 +82,16 @@ class _ConfirmationPopupState extends State { border: OutlineInputBorder(), suffixText: "€", ), - keyboardType: TextInputType.number, + keyboardType: TextInputType.numberWithOptions( + decimal: true, + ), 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/form_popup.dart b/lib/ui/add_page/widgets/form_popup.dart index 7f6af10..59368e0 100644 --- a/lib/ui/add_page/widgets/form_popup.dart +++ b/lib/ui/add_page/widgets/form_popup.dart @@ -145,12 +145,16 @@ class _ManualEANPopupState extends State<_ManualEANPopup> { border: OutlineInputBorder(), suffixText: "€", ), - keyboardType: TextInputType.number, + keyboardType: TextInputType.numberWithOptions( + decimal: true, + ), 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; }, @@ -273,12 +277,16 @@ class _FullyManualState extends State<_FullyManual> { border: OutlineInputBorder(), suffixText: "€", ), - keyboardType: TextInputType.number, + keyboardType: TextInputType.numberWithOptions( + decimal: true, + ), 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/sell_page/widgets/sell_page.dart b/lib/ui/sell_page/widgets/sell_page.dart index 46b2938..dd0404b 100644 --- a/lib/ui/sell_page/widgets/sell_page.dart +++ b/lib/ui/sell_page/widgets/sell_page.dart @@ -129,7 +129,9 @@ class _SellPageState extends State { suffixText: "€", border: OutlineInputBorder(), ), - keyboardType: TextInputType.number, + keyboardType: TextInputType.numberWithOptions( + decimal: true, + ), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 97d2e53..39a3563 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -293,8 +293,16 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" - logging: + logger: dependency: "direct main" + description: + name: logger + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + logging: + dependency: transitive description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 diff --git a/pubspec.yaml b/pubspec.yaml index 2c18afe..fc17ee5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,7 +41,6 @@ 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 @@ -51,6 +50,7 @@ dependencies: intl: ^0.20.2 flutter_launcher_icons: ^0.14.4 fl_chart: ^1.0.0 + logger: ^2.6.1 dev_dependencies: flutter_test: