From 1322bd4eac3ec0743de6a27a4eb6e713dd1b7c2d Mon Sep 17 00:00:00 2001 From: yousefyako Date: Thu, 12 Oct 2023 13:29:52 +0200 Subject: [PATCH] Implemented new transaction functionality --- lib/models/transaction.dart | 83 +++++++++++++++++ lib/services/local_data.dart | 4 +- lib/services/transaction_api_service.dart | 89 ++++++++++++------- lib/ui/screens/home_page/home_viewmodel.dart | 4 +- .../snappingcheet_viewmodel.dart | 33 +++---- lib/ui/sheets/top_sheet/top_sheet_view.dart | 11 ++- .../top_sheet/top_sheet_view_model.dart | 74 ++++++++------- pubspec.lock | 1 - 8 files changed, 207 insertions(+), 92 deletions(-) create mode 100644 lib/models/transaction.dart diff --git a/lib/models/transaction.dart b/lib/models/transaction.dart new file mode 100644 index 0000000..44201d4 --- /dev/null +++ b/lib/models/transaction.dart @@ -0,0 +1,83 @@ +/// The class is a model for a transaction. It has a bunch of properties, +/// and a constructor that takes a map of strings and dynamic values +class Transaction { + String klarnaClientToken = ""; + String klarnaSessionID = ""; + int transactionID = -1; + int startTimestamp = -1; + int kwhTransferred = -1; + int currentChargePercentage = -1; + int pricePerKwh = -1; + int connectorID = -1; + String userID = ""; + int price = -1; + int endTimeStamp = -1; + int discount = -1; + + Transaction(); + + @override + String toString() { + return 'Transaction ID: $transactionID startTimeStamp: $startTimestamp endTimeStamp: $endTimeStamp'; + } + + /// The function `updateFrom` updates the current object's properties with the corresponding properties + /// from another `Transaction` object if they are not empty or have a specific value. + /// + /// Args: + /// other (Transaction): The "other" parameter is an instance of the Transaction class that contains + /// the updated values for the transaction attributes. + void updateFrom(Transaction other) { + if (other.klarnaClientToken != "") { + klarnaClientToken = other.klarnaClientToken; + } + if (other.klarnaSessionID != "") { + klarnaSessionID = other.klarnaSessionID; + } + if (other.transactionID != -1) { + transactionID = other.transactionID; + } + if (other.startTimestamp != -1) { + startTimestamp = other.startTimestamp; + } + if (other.kwhTransferred != -1) { + kwhTransferred = other.kwhTransferred; + } + if (other.currentChargePercentage != -1) { + currentChargePercentage = other.currentChargePercentage; + } + if (other.pricePerKwh != -1) { + pricePerKwh = other.pricePerKwh; + } + if (other.connectorID != -1) { + connectorID = other.connectorID; + } + if (other.userID != "") { + userID = other.userID; + } + if (other.price != -1) { + price = other.price; + } + if (other.endTimeStamp != -1) { + endTimeStamp = other.endTimeStamp; + } + if (other.discount != -1) { + discount = other.discount; + } + } + + Transaction.fromJson(Map json) { + transactionID = json['transactionID'] ?? 0; + klarnaClientToken = json['klarnaClientToken'] ?? ""; + klarnaSessionID = json['klarnaSessionID'] ?? ""; + startTimestamp = json['startTimestamp'] ?? 0; + kwhTransferred = json['kwhTransferred'] ?? 0; + currentChargePercentage = json['currentChargePercentage'] ?? 0; + pricePerKwh = json['pricePerKWh'] ?? 0; + connectorID = json['connectorID'] ?? 0; + userID = json['userID'] ?? ""; + price = json['price'] ?? 0; + endTimeStamp = json['endTimestamp'] ?? -1; + discount = json['discount'] ?? 0; + } +} diff --git a/lib/services/local_data.dart b/lib/services/local_data.dart index 61e2f16..0ae1e4c 100644 --- a/lib/services/local_data.dart +++ b/lib/services/local_data.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flexicharge/enums/event_type.dart'; import 'package:flexicharge/models/charger.dart'; import 'package:flexicharge/models/charger_point.dart'; -import 'package:flexicharge/models/old_transaction.dart'; +import 'package:flexicharge/models/transaction.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; /// It's a class that holds data that is used by the app @@ -13,7 +13,7 @@ class LocalData { List chargerPoints = []; LatLng userLocation = LatLng(0, 0); int chargingCharger = -1; - OldTransaction transactionSession = OldTransaction(); + Transaction transactionSession = Transaction(); bool isButtonActive = true; int chargingPercentage = 0; //late Timer timer; This is commented out because a temporary diff --git a/lib/services/transaction_api_service.dart b/lib/services/transaction_api_service.dart index d4a549a..c92bf33 100644 --- a/lib/services/transaction_api_service.dart +++ b/lib/services/transaction_api_service.dart @@ -1,7 +1,8 @@ import 'dart:convert'; import 'package:flexicharge/enums/error_codes.dart'; import 'package:flexicharge/models/api.dart'; -import 'package:flexicharge/models/old_transaction.dart'; +import 'package:flexicharge/models/transaction.dart'; +import 'package:flexicharge/models/user_secure_storage.dart'; import 'package:http/http.dart' as http; /// It's a class that makes requests to the server and returns a Transaction object @@ -16,13 +17,21 @@ class TransactionApiService { /// /// Returns: /// A Future - Future getTransactionById(int id) async { - var response = await client.get(Uri.parse('${API.url}/transactions/$id')); + Future getTransactionById(int id) async { + String? accessToken = await UserSecureStorage.getUserAccessToken(); + + var response = await client.get( + Uri.parse('${API.url}/transaction/$id'), + headers: { + ...API.defaultRequestHeaders, + 'Authorization': 'Bearer $accessToken' + }, + ); switch (response.statusCode) { case 200: var parsedTransaction = json.decode(response.body); - var transactionFromJson = OldTransaction.fromJson(parsedTransaction); + var transactionFromJson = Transaction.fromJson(parsedTransaction); return transactionFromJson; case 404: throw Exception(ErrorCodes.notFound); @@ -41,8 +50,8 @@ class TransactionApiService { /// /// Returns: /// A list of transactions. - Future> getTransactionsByUserId(int id) async { - var transactions = []; + Future> getTransactionsByUserId(int id) async { + var transactions = []; var response = await client .get(Uri.parse('${API.url}/transactions/userTransactions/$id')); @@ -50,7 +59,7 @@ class TransactionApiService { case 200: var parsedTransactions = json.decode(response.body) as List; for (var trans in parsedTransactions) { - transactions.add(OldTransaction.fromJson(trans)); + transactions.add(Transaction.fromJson(trans)); } return transactions; case 404: @@ -70,8 +79,8 @@ class TransactionApiService { /// /// Returns: /// A list of transactions. - Future> getTransactionsByChargerId(int id) async { - var transactions = []; + Future> getTransactionsByChargerId(int id) async { + var transactions = []; var response = await client .get(Uri.parse('${API.url}/transactions/chargerTransactions/$id')); @@ -79,7 +88,7 @@ class TransactionApiService { case 200: var parsedTransactions = json.decode(response.body) as List; for (var trans in parsedTransactions) { - transactions.add(OldTransaction.fromJson(trans)); + transactions.add(Transaction.fromJson(trans)); } return transactions; case 404: @@ -160,21 +169,23 @@ class TransactionApiService { /// /// Returns: /// The response is a JSON object containing the following: - Future createKlarnaPaymentSession( + Future createKlarnaPaymentSession( int? userId, int chargerId) async { - var response = await client.post(Uri.parse('${API.url}/transactions'), - headers: API.defaultRequestHeaders, + String? accessToken = await UserSecureStorage.getUserAccessToken(); + var response = await client.post(Uri.parse('${API.url}/transaction'), + headers: { + ...API.defaultRequestHeaders, + 'Authorization': 'Bearer $accessToken' + }, encoding: Encoding.getByName('utf-8'), - //These parameters do not appear to make a difference - //since the functionality on the backend is not implemented. body: json.encode({ - 'chargerID': chargerId, - 'isKlarnaPayment': true + 'connectorID': chargerId, + 'paymentType': "klarna" })); switch (response.statusCode) { case 201: var transaction = json.decode(response.body) as Map; - var parsedSession = OldTransaction.fromJson(transaction); + var parsedSession = Transaction.fromJson(transaction); return parsedSession; case 400: throw Exception(ErrorCodes.badRequest); @@ -189,13 +200,17 @@ class TransactionApiService { /// The request returns the updated transaction object, /// If everything goes as expected, it will contain a paymentId. - Future createKlarnaOrder( + Future createKlarnaOrder( int transactionId, String authToken, ) async { + String? accessToken = await UserSecureStorage.getUserAccessToken(); var response = await client.put( - Uri.parse('${API.url}/transactions/start/$transactionId'), - headers: API.defaultRequestHeaders, + Uri.parse('${API.url}/transaction/start/$transactionId'), + headers: { + ...API.defaultRequestHeaders, + 'Authorization': 'Bearer $accessToken' + }, body: jsonEncode({ 'transactionID': transactionId, 'authorization_token': authToken @@ -203,12 +218,13 @@ class TransactionApiService { switch (response.statusCode) { case 200: + print(response.body); var transaction = json.decode(response.body) as Map; if (transaction.isEmpty) throw Exception(ErrorCodes.emptyResponse); - var parsedSession = OldTransaction.fromJson(transaction); + var parsedSession = Transaction.fromJson(transaction); print("Klarna updatedSession paymentID: " + - parsedSession.paymentID.toString()); + parsedSession.klarnaSessionID.toString()); return parsedSession; case 400: print(response.body); @@ -226,19 +242,28 @@ class TransactionApiService { /// The request will return an updated transaction object which contains /// paymentConfirmed == true. - Future stopCharging(int transactionId) async { + Future stopCharging(int transactionId) async { + String? accessToken = await UserSecureStorage.getUserAccessToken(); + var response = await client.put( - Uri.parse('${API.url}/transactions/stop/$transactionId'), - headers: API.defaultRequestHeaders); + Uri.parse('${API.url}/transaction/stop/$transactionId'), + headers: { + ...API.defaultRequestHeaders, + 'Authorization': 'Bearer $accessToken' + }, + ); + + print("==============================================" + + response.statusCode.toString()); + print(response.statusCode); switch (response.statusCode) { case 200: - // We get a List with a single Transaction object in response - var list = json.decode(response.body) as List; - if (list.isEmpty) throw Exception(ErrorCodes.emptyResponse); - var parsedSession = OldTransaction.fromJson(list.first); - print("Klarna updatedSession paymentConfirmed : " + - parsedSession.paymentConfirmed.toString()); + var decodedRespBody = json.decode(response.body); + print("====================================="); + print(decodedRespBody); + if (decodedRespBody.isEmpty) throw Exception(ErrorCodes.emptyResponse); + var parsedSession = Transaction.fromJson(decodedRespBody); return parsedSession; case 400: throw Exception(ErrorCodes.badRequest); diff --git a/lib/ui/screens/home_page/home_viewmodel.dart b/lib/ui/screens/home_page/home_viewmodel.dart index d8ce379..c4c25c9 100644 --- a/lib/ui/screens/home_page/home_viewmodel.dart +++ b/lib/ui/screens/home_page/home_viewmodel.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'package:flexicharge/models/old_transaction.dart'; +import 'package:flexicharge/models/transaction.dart'; import 'package:flexicharge/services/transaction_api_service.dart'; import 'package:flexicharge/app/app.router.dart'; import 'package:flexicharge/models/charger_point.dart'; @@ -121,7 +121,7 @@ class HomeViewModel extends BaseViewModel { /// The current charging percentage of the car. Future fetchChargingPercentage() async { try { - OldTransaction currentTransaction = await _transactionAPI + Transaction currentTransaction = await _transactionAPI .getTransactionById(localData.transactionSession.transactionID); int currentChargingPercentage = diff --git a/lib/ui/sheets/map_bottom_sheet/snappingcheet_viewmodel.dart b/lib/ui/sheets/map_bottom_sheet/snappingcheet_viewmodel.dart index 074e375..4d46fc8 100644 --- a/lib/ui/sheets/map_bottom_sheet/snappingcheet_viewmodel.dart +++ b/lib/ui/sheets/map_bottom_sheet/snappingcheet_viewmodel.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flexicharge/app/app.locator.dart'; import 'package:flexicharge/models/charger.dart'; import 'package:flexicharge/models/charger_point.dart'; -import 'package:flexicharge/models/old_transaction.dart'; +import 'package:flexicharge/models/transaction.dart'; import 'package:flexicharge/services/charger_api_service.dart'; import 'package:flexicharge/services/local_data.dart'; import 'package:flexicharge/services/transaction_api_service.dart'; @@ -255,13 +255,13 @@ class CustomSnappingSheetViewModel extends BaseViewModel { print("starting the session.."); // Create a transaction session print("Trying to create a transaction session... "); - OldTransaction transactionSession = + Transaction transactionSession = await _transactionAPI.createKlarnaPaymentSession(null, id); localData.transactionSession = transactionSession; print("TransactionID: " + localData.transactionSession.transactionID.toString()); print("clientToken: " + - localData.transactionSession.clientToken.toString()); + localData.transactionSession.klarnaClientToken.toString()); print('Done'); @@ -270,7 +270,7 @@ class CustomSnappingSheetViewModel extends BaseViewModel { print("Getting auth token..."); String authToken = - await _startKlarnaActivity(transactionSession.clientToken); + await _startKlarnaActivity(transactionSession.klarnaClientToken); print("authToken: " + authToken); print("auth token: " + authToken); @@ -284,21 +284,12 @@ class CustomSnappingSheetViewModel extends BaseViewModel { await _transactionAPI.createKlarnaOrder( transactionSession.transactionID, authToken); - int numberOfCalls = 100; - // Start the loop - for (int i = 0; i < numberOfCalls; i++) { - // Calculate the delay for each call - Duration delay = Duration(seconds: i * 3); + Transaction specificTransaction = await _transactionAPI + .getTransactionById(transactionSession.transactionID); + localData.transactionSession.updateFrom(specificTransaction); - // Schedule the call after the delay - Timer(delay, () async { - localData.transactionSession = await _transactionAPI - .getTransactionById(transactionSession.transactionID); - }); - } - - print( - "payment ID" + localData.transactionSession.paymentID.toString()); + print("payment ID" + + localData.transactionSession.klarnaSessionID.toString()); } } catch (e) { print(e); @@ -312,10 +303,10 @@ class CustomSnappingSheetViewModel extends BaseViewModel { try { // Reserve charger during payment print("trying to disconnect the charger..."); - localData.transactionSession = await _transactionAPI.stopCharging(id); + Transaction stoppedChargingSession = + await _transactionAPI.stopCharging(id); + localData.transactionSession.updateFrom(stoppedChargingSession); print("charger is disconnected"); - print("paymentConfirmed: " + - localData.transactionSession.paymentConfirmed.toString()); } catch (e) { print(e); } diff --git a/lib/ui/sheets/top_sheet/top_sheet_view.dart b/lib/ui/sheets/top_sheet/top_sheet_view.dart index 321a84a..13ceeca 100644 --- a/lib/ui/sheets/top_sheet/top_sheet_view.dart +++ b/lib/ui/sheets/top_sheet/top_sheet_view.dart @@ -68,7 +68,7 @@ class TopSheetView extends StatelessWidget { batteryPercent: model.currentChargePercentage, chargingAdress: model.chargingAdress, timeUntilFullyCharged: - model.transactionSession.timestamp.parseTimeDiff(), + "1hr 21min until full", // this is hardcoded because we don't get it from backend yet kilowattHours: model.kilowattHours, ), ), @@ -114,12 +114,15 @@ class TopSheetView extends StatelessWidget { if (model.chargingState == 4 && model.topSheetState == 3) Expanded( child: ChargingSummary( - time: model.getChargingStopTime(), - chargingDuration: "1hr 41min", + time: model.getChargingStopTime( + model.localData.transactionSession.endTimeStamp), + chargingDuration: model.getDuration( + model.localData.transactionSession.startTimestamp, + model.localData.transactionSession.endTimeStamp), energyUsed: "${model.transactionSession.kwhTransferred.toStringAsFixed(2)}kWh @ ${model.transactionSession.pricePerKwh.toStringAsFixed(2)}kr", totalCost: - "${(model.transactionSession.kwhTransferred * model.transactionSession.pricePerKwh).toStringAsFixed(2)}kr", + "${model.transactionSession.price.toStringAsFixed(2)}kr", stopCharging: complete, ), ), diff --git a/lib/ui/sheets/top_sheet/top_sheet_view_model.dart b/lib/ui/sheets/top_sheet/top_sheet_view_model.dart index b128e00..c7d300a 100644 --- a/lib/ui/sheets/top_sheet/top_sheet_view_model.dart +++ b/lib/ui/sheets/top_sheet/top_sheet_view_model.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flexicharge/app/app.locator.dart'; import 'package:flexicharge/enums/event_type.dart'; import 'package:flexicharge/enums/top_sheet_strings.dart'; -import 'package:flexicharge/models/old_transaction.dart'; +import 'package:flexicharge/models/transaction.dart'; import 'package:flexicharge/services/charger_api_service.dart'; import 'package:flexicharge/services/local_data.dart'; import 'package:flexicharge/services/transaction_api_service.dart'; @@ -30,27 +30,14 @@ class TopSheetViewModel extends BaseViewModel { init() { startStreamListener(); - timer = Timer.periodic(Duration(milliseconds: 200), - (Timer t) => incrementChargingPercentage()); - } - - /// If the charging percentage is less than 100, increment it by 1. - /// If it's not, cancel the timer and change the charging state to true - void incrementChargingPercentage() { - if (localData.chargingPercentage < 100) { - localData.chargingPercentage += 1; - notifyListeners(); - } else { - timer?.cancel(); - changeChargingState(true); - } + changeChargingState(false); } /// The `chargingAddress` getter is returning the address of the charger. String get chargingAdress { var chargerPoint = localData.chargerPoints.firstWhere((element) => element .chargers - .any((element) => element.id == transactionSession.chargerID)); + .any((element) => element.id == transactionSession.connectorID)); return chargerPoint.name; } @@ -80,7 +67,7 @@ class TopSheetViewModel extends BaseViewModel { changeTopSheetSize(); } - OldTransaction get transactionSession => localData.transactionSession; + Transaction get transactionSession => localData.transactionSession; /// It changes the size of the top sheet based on the state of the top sheet void changeTopSheetSize() { @@ -116,8 +103,9 @@ class TopSheetViewModel extends BaseViewModel { chargingState = 4; changeTopSheetState(3); try { - await transactionApiService - .stopCharging(localData.transactionSession.transactionID); + Transaction stoppedChargingSession = + await transactionApiService.stopCharging(9999); // hardcoded + localData.transactionSession.updateFrom(stoppedChargingSession); } catch (error) { print("Could not stop charging"); } @@ -198,21 +186,47 @@ class TopSheetViewModel extends BaseViewModel { }); } - /// The function returns the current time in the format "HH:MM". + /// The function `getChargingStopTime` takes an end timestamp and returns the time in hours and minutes, + /// adjusted by 2 hours. + /// + /// Args: + /// endTimeStamp (int): The `endTimeStamp` parameter is a Unix timestamp representing the end time of + /// a charging session. + /// + /// Returns: + /// a string representing the time when charging should stop. + String getChargingStopTime(int endTimeStamp) { + DateTime realTime = + DateTime.fromMillisecondsSinceEpoch(endTimeStamp * 1000, isUtc: true); + + String timeNow = + (realTime.hour + 2).toString() + ":" + (realTime.minute).toString(); + + return timeNow; + } + + /// The function calculates the duration between two timestamps in hours and minutes. /// - /// This method is temporarily used to retrieve the charging stop time until the stream listeners is - /// used correctly in the app. + /// Args: + /// startTimeStamp (int): The startTimeStamp parameter represents the starting time of an event or + /// duration in seconds since the Unix epoch (January 1, 1970, 00:00:00 UTC). + /// endTimeStamp (int): The `endTimeStamp` parameter represents the end time of an event or duration + /// in seconds since the Unix epoch (January 1, 1970). /// /// Returns: - /// The method is returning a string representing the current hour and minute in the format "HH:MM". - String getChargingStopTime() { - int hour = DateTime.now().hour; - int minute = DateTime.now().minute; + /// a string that represents the duration between the start and end timestamps in the format "X hr Y + /// min", where X is the number of hours and Y is the number of minutes. + String getDuration(int startTimeStamp, int endTimeStamp) { + DateTime startTime = startTimeStamp.parseUNIXTimestamp(); + DateTime endTime = endTimeStamp.parseUNIXTimestamp(); + + Duration difference = endTime.difference(startTime); - String hourString = hour < 10 ? '0$hour' : '$hour'; - String minuteString = minute < 10 ? '0$minute' : '$minute'; + int hours = difference.inHours; + int minutes = difference.inMinutes.remainder(60); - return '$hourString:$minuteString'; + String totalDuration = '$hours hr ${minutes}min'; + return totalDuration; } } @@ -220,7 +234,7 @@ class TopSheetViewModel extends BaseViewModel { /// two dates. extension TimeParser on int { DateTime parseUNIXTimestamp() { - return DateTime.fromMicrosecondsSinceEpoch(this * 1000); + return DateTime.fromMillisecondsSinceEpoch(this * 1000, isUtc: true); } String parseTimeDiff() { diff --git a/pubspec.lock b/pubspec.lock index d581004..e25fddc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -400,7 +400,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - geolocator: dependency: "direct main" description: