diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5249fe5..c62a44f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,11 +41,20 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - Firebase/Analytics (11.15.0): + - Firebase/Core + - Firebase/Core (11.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 11.15.0) - Firebase/CoreOnly (11.15.0): - FirebaseCore (~> 11.15.0) - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - FirebaseMessaging (~> 11.15.0) + - firebase_analytics (11.6.0): + - Firebase/Analytics (= 11.15.0) + - firebase_core + - Flutter - firebase_core (3.15.2): - Firebase/CoreOnly (= 11.15.0) - Flutter @@ -53,6 +62,24 @@ PODS: - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter + - FirebaseAnalytics (11.15.0): + - FirebaseAnalytics/Default (= 11.15.0) + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleAppMeasurement/Default (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) - FirebaseCore (11.15.0): - FirebaseCoreInternal (~> 11.15.0) - GoogleUtilities/Environment (~> 8.1) @@ -371,14 +398,16 @@ SPEC CHECKSUMS: DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_analytics: bf93e20703c95030404d6ddbb1adf05bf5c3885b firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 + FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f @@ -387,6 +416,8 @@ SPEC CHECKSUMS: geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 + GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 + GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 @@ -423,4 +454,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5df9d8aa8f2c105eacd5ad7a310503d93c68c86b -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index fbf63b2..c3862e2 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -2,21 +2,25 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io' show Platform; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:http/http.dart' as http; +import '../exceptions/api_failure.dart'; + abstract class ApiClient { Future post( String endPoint, { required Map body, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, + required Function(dynamic response, int statusCode, {int? messageStatus}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, bool isAllowAny, bool isExternal, bool isRCService, @@ -32,61 +36,69 @@ abstract class ApiClient { bool isRCService, }); - Future simplePost( - String fullUrl, { - required Map body, - required Map headers, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - }); - - Future simpleGet( - String fullUrl, { - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers, - }); - - Future simplePut( - String fullUrl, { - Map? body, - Map? headers, - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - }); - - Future simpleDelete( - String fullUrl, { - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers, - }); - - Future handleUnauthorized(int statusCode, {required String forUrl}); - String getSessionId(String id); - Future generatePackagesToken(); +// Future simplePost( +// String fullUrl, { +// required Map body, +// required Map headers, +// required Function(dynamic response, int statusCode) onSuccess, +// required Function(String error, int statusCode) onFailure, +// }); + +// +// Future simpleGet( +// String fullUrl, { +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// Map? queryParams, +// Map? headers, +// }); +// +// Future simplePut( +// String fullUrl, { +// Map? body, +// Map? headers, +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// }); +// +// Future simpleDelete( +// String fullUrl, { +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// Map? queryParams, +// Map? headers, +// }); + +// Future handleUnauthorized(int statusCode, {required String forUrl}); +// Future generatePackagesToken(); } class ApiClientImp implements ApiClient { final _analytics = getIt(); final LoggerService loggerService; + final AppState appState; + final DialogService dialogService; - ApiClientImp({required this.loggerService}); + ApiClientImp({ + required this.loggerService, + required this.dialogService, + required this.appState, + }); @override - post(String endPoint, - {required Map body, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - bool isAllowAny = false, - bool isExternal = false, - bool isRCService = false, - bool bypassConnectionCheck = false}) async { + post( + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool bypassConnectionCheck = false, + }) async { AppState appState = getIt.get(); String url; if (isExternal) { @@ -103,7 +115,7 @@ class ApiClientImp implements ApiClient { Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; if (!isExternal) { String? token = appState.appAuthToken; - String? languageID = (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en') ?? 'ar'; + String? languageID = (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en'); if (endPoint == ApiConsts.sendActivationCode) { languageID = 'en'; } @@ -113,7 +125,6 @@ class ApiClientImp implements ApiClient { if (body.containsKey('LanguageID')) { if (body['LanguageID'] != null) { - //change this line because language issue happened on dental body['LanguageID'] = body['LanguageID'] == 'ar' ? 1 : body['LanguageID'] == 'en' @@ -128,7 +139,6 @@ class ApiClientImp implements ApiClient { : IS_DENTAL_ALLOWED_BACKEND; } - //Todo: I have converted it to string body['DeviceTypeID'] = Platform.isIOS ? "1" @@ -160,40 +170,38 @@ class ApiClientImp implements ApiClient { } } - - body['LanguageID'] = body['LanguageID'] ?? "2"; - body['VersionID'] = body['VersionID'] ?? "18.7"; - body['Channel'] = body['Channel'] ?? "3"; - body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; - body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; - body['Latitude'] = body['Latitude'] ?? "0.0"; - body['Longitude'] = body['Longitude'] ?? "0.0"; + body['LanguageID'] = body['LanguageID'] ?? "2"; + body['VersionID'] = body['VersionID'] ?? "18.7"; + body['Channel'] = body['Channel'] ?? "3"; + body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; + body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; + body['Latitude'] = body['Latitude'] ?? "0.0"; + body['Longitude'] = body['Longitude'] ?? "0.0"; body['DeviceTypeID'] = body['DeviceTypeID'] ?? - - - (Platform.isIOS ? "1" : await Utils.isGoogleServicesAvailable() ? "2" : "3"); - //"LanguageID":1,"VersionID":18.7,"Channel":3,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","Latitude":0.0,"Longitude":0.0,"DeviceTypeID":2,"PatientType":1} + (Platform.isIOS + ? "1" + : await Utils.isGoogleServicesAvailable() + ? "2" + : "3"); body.removeWhere((key, value) => value == null); - - log("bodi: ${json.encode(body)}"); - log("bodi: ${Uri.parse(url.trim())}"); + log("body: ${json.encode(body)}"); + log("uri: ${Uri.parse(url.trim())}"); if (await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck)) { - final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); final int statusCode = response.statusCode; if (statusCode < 200 || statusCode >= 400) { - onFailure('Error While Fetching data', statusCode); + onFailure('Error While Fetching data', statusCode, failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { - var parsed = json.decode(utf8.decode(response.bodyBytes)); + var parsed = json.decode(utf8.decode(response.bodyBytes)); log("parsed: ${parsed.toString()}"); if (isAllowAny) { onSuccess(parsed, statusCode); } else { if (parsed['Response_Message'] != null) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update @@ -204,19 +212,18 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['IsAuthenticated'] == null) { if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['MessageStatus'] == 1) { onSuccess(parsed, statusCode); } else if (parsed['Result'] == 'OK') { onSuccess(parsed, statusCode); } else { - - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, + failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); - } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { onSuccess(parsed, statusCode); @@ -226,28 +233,46 @@ class ApiClientImp implements ApiClient { } else { if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { - onFailure("Server Error found with no available message", statusCode); + onFailure( + "Server Error found with no available message", + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { - onFailure(parsed['ErrorSearchMsg'], statusCode); + onFailure( + parsed['ErrorSearchMsg'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure(parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure( + parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } } - } - - else { + } else { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode); } else { if (parsed['message'] != null) { - onFailure(parsed['message'] ?? parsed['message'], statusCode); + onFailure( + parsed['message'] ?? parsed['message'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } else { - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } @@ -256,13 +281,17 @@ class ApiClientImp implements ApiClient { } } } else { - onFailure('Please Check The Internet Connection 1', -1); + onFailure( + 'Please Check The Internet Connection 1', + -1, + failureType: ConnectivityFailure("Error While Fetching data"), + ); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } catch (e) { loggerService.errorLogs(e.toString()); if (e.toString().contains("ClientException")) { - onFailure('Something went wrong, plase try again', -1); + onFailure('Something went wrong, plase try again', -1, failureType: InvalidCredentials('Something went wrong, plase try again')); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } else { onFailure(e.toString(), -1); @@ -271,6 +300,7 @@ class ApiClientImp implements ApiClient { } } + @override get(String endPoint, {required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, @@ -304,203 +334,173 @@ class ApiClientImp implements ApiClient { // print("statusCode :$statusCode"); if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); + onFailure('Error While Fetching data', statusCode); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); - onSuccess!(parsed, statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simplePost( - String fullUrl, { - required Map body, - required Map headers, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - }) async { - String url = fullUrl; - // print("URL Query String: $url"); - // print("body: $body"); - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.post( - Uri.parse(url.trim()), - body: json.encode(body), - headers: headers, - ); - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePost(fullUrl, onFailure: onFailure, onSuccess: onSuccess, body: body, headers: headers); - - // print(response.body.toString()); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); + onSuccess(parsed, statusCode); } } else { - onFailure!('Please Check The Internet Connection', -1); + onFailure('Please Check The Internet Connection', -1); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } - simpleGet(String fullUrl, - {Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers}) async { - headers = headers ?? {}; - String url = fullUrl; - - var haveParams = (queryParams != null); - if (haveParams) { - String queryString = Uri(queryParameters: queryParams).query; - url += '?$queryString'; - // print("URL Query String: $url"); - } - - if (await Utils.checkConnection()) { - headers.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.get( - Uri.parse(url.trim()), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleGet(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, queryParams: queryParams); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simplePut(String fullUrl, - {Map? body, - Map? headers, - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure}) async { - String url = fullUrl; - // print("URL Query String: $url"); - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.put( - Uri.parse(url.trim()), - body: json.encode(body), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePut(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, body: body); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simpleDelete(String fullUrl, - {Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers}) async { - String url = fullUrl; - // print("URL Query String: $url"); - - var haveParams = (queryParams != null); - if (haveParams) { - String queryString = Uri(queryParameters: queryParams).query; - url += '?$queryString'; - // print("URL Query String: $url"); - } - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.delete( - Uri.parse(url.trim()), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleDelete(fullUrl, onFailure: onFailure, onSuccess: onSuccess, queryParams: queryParams, headers: headers); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - Future handleUnauthorized(int statusCode, {required String forUrl}) async { - if (forUrl.startsWith(EXA_CART_API_BASE_URL) && statusCode == 401) { - final token = await generatePackagesToken(); - ApiConsts.packagesAuthHeader['Authorization'] = 'Bearer $token'; - return (token is String); - } - return false; - } + // @override + // simplePost( + // String fullUrl, { + // required Map body, + // required Map headers, + // required Function(dynamic response, int statusCode) onSuccess, + // required Function(String error, int statusCode) onFailure, + // }) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // // print("body: $body"); + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.post( + // Uri.parse(url.trim()), + // body: json.encode(body), + // headers: headers, + // ); + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) { + // simplePost(fullUrl, onFailure: onFailure, onSuccess: onSuccess, body: body, headers: headers); + // } + // + // // print(response.body.toString()); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + + // simpleGet(String fullUrl, + // {Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure, + // Map? queryParams, + // Map? headers}) async { + // headers = headers ?? {}; + // String url = fullUrl; + // + // var haveParams = (queryParams != null); + // if (haveParams) { + // String queryString = Uri(queryParameters: queryParams).query; + // url += '?$queryString'; + // // print("URL Query String: $url"); + // } + // + // if (await Utils.checkConnection()) { + // headers.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.get( + // Uri.parse(url.trim()), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simpleGet(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, queryParams: queryParams); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + + // simplePut(String fullUrl, + // {Map? body, + // Map? headers, + // Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure}) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.put( + // Uri.parse(url.trim()), + // body: json.encode(body), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simplePut(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, body: body); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + // + // simpleDelete(String fullUrl, + // {Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure, + // Map? queryParams, + // Map? headers}) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // + // var haveParams = (queryParams != null); + // if (haveParams) { + // String queryString = Uri(queryParameters: queryParams).query; + // url += '?$queryString'; + // // print("URL Query String: $url"); + // } + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.delete( + // Uri.parse(url.trim()), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simpleDelete(fullUrl, onFailure: onFailure, onSuccess: onSuccess, queryParams: queryParams, headers: headers); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + @override String getSessionId(String id) { return id.replaceAll(RegExp('/[^a-zA-Z]'), ''); } - Future generatePackagesToken() async { - var url = EXA_CART_API_BASE_URL + PACKAGES_TOKEN; - var body = { - "api_client": { - "client_id": "a4ab6be4-424f-4836-b032-46caed88e184", - "client_secret": "3c1a3e07-4a40-4510-9fb0-ee5f0a72752c" - } - }; - String? token; - final completer = Completer(); - simplePost(url, body: body, headers: {}, onSuccess: (dynamic stringResponse, int statusCode) { - if (statusCode == 200) { - var jsonResponse = json.decode(stringResponse); - token = jsonResponse['auth_token']; - completer.complete(); - } - }, onFailure: (String error, int statusCode) { - completer.complete(); - logApiFullUrlError(url, error, statusCode); - }); - await completer.future; - return token!; - } - logApiFullUrlError(String fullUrl, error, code) { final endpoint = Uri.parse(fullUrl).pathSegments.last; logApiEndpointError(endpoint, error, code); diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 91e200a..bdf8d25 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,15 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/post_params_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; -import 'package:hmg_patient_app_new/main.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'api_consts.dart' as ApiConsts; - class AppState { - // Simple constructor - let get_it handle the singleton behavior - AppState(); + NavigationService navigationService; + AppState({required this.navigationService}); bool isAuthenticated = true; @@ -40,9 +39,7 @@ class AppState { PostParamsModel? get postParamsObject => _postParams; - Map get postParamsJson => isAuthenticated - ? (_postParams?.toJsonAfterLogin() ?? {}) - : (_postParams?.toJson() ?? {}); + Map get postParamsJson => isAuthenticated ? (_postParams?.toJsonAfterLogin() ?? {}) : (_postParams?.toJson() ?? {}); void setPostParamsModel(PostParamsModel _postParams) { this._postParams = _postParams; @@ -51,12 +48,9 @@ class AppState { double userLat = 0.0; double userLong = 0.0; - bool isArabic() => - EasyLocalization.of(navigatorKey.currentContext!)?.locale.languageCode == - "ar"; + bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar"; - int getLanguageID(context) => - EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; AuthenticatedUser? _authenticatedUser; diff --git a/lib/core/common_models/generic_api_model.dart b/lib/core/common_models/generic_api_model.dart new file mode 100644 index 0000000..1ab11db --- /dev/null +++ b/lib/core/common_models/generic_api_model.dart @@ -0,0 +1,34 @@ +class GenericApiModel { + final int? messageStatus; + final String? errorMessage; + final int? statusCode; + final T? data; + + GenericApiModel({ + this.messageStatus, + this.errorMessage, + this.statusCode, + this.data, + }); + + factory GenericApiModel.fromJson( + Map json, + T Function(Object? json)? fromJsonT, + ) { + return GenericApiModel( + messageStatus: json['messageStatus'] as int?, + errorMessage: json['errorMessage'] as String?, + statusCode: json['statusCode'] as int?, + data: fromJsonT != null ? fromJsonT(json['data']) : json['data'] as T?, + ); + } + + Map toJson(Object Function(T value)? toJsonT) { + return { + 'messageStatus': messageStatus, + 'errorMessage': errorMessage, + 'statusCode': statusCode, + 'data': toJsonT != null && data != null ? toJsonT(data as T) : data, + }; + } +} diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 440cd26..5aba3e2 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -1,13 +1,18 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -15,25 +20,58 @@ GetIt getIt = GetIt.instance; class AppDependencies { static Future addDependencies() async { - // Services - getIt.registerLazySingleton(() => LoggerServiceImp(logger: Logger(printer: PrettyPrinter( - methodCount: 2, // number of stack trace lines - errorMethodCount: 5, // number of stack trace lines for errors - lineLength: 100, // wrap width - colors: true, // colorful logs - printEmojis: true, // include emojis - ),))); + Logger logger = Logger( + printer: PrettyPrinter( + methodCount: 2, + errorMethodCount: 5, + lineLength: 100, + colors: true, + printEmojis: true, + ), + ); + + // Core Services + getIt.registerLazySingleton(() => LoggerServiceImp(logger: logger)); + getIt.registerLazySingleton(() => NavigationService()); + getIt.registerLazySingleton(() => GAnalytics()); + getIt.registerLazySingleton(() => AppState(navigationService: getIt())); + getIt.registerLazySingleton(() => LocationUtils(isShowConfirmDialog: false, navigationService: getIt())); + getIt.registerLazySingleton(() => DialogServiceImp(navigationService: getIt())); + getIt.registerLazySingleton(() => ErrorHandlerServiceImp( + dialogService: getIt(), + loggerService: getIt(), + navigationService: getIt(), + )); + final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences)); - getIt.registerSingleton(AppState()); - getIt.registerSingleton(GAnalytics()); - getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt())); + getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt(), dialogService: getIt(), appState: getIt())); // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); getIt.registerLazySingleton(() => AuthenticationRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton(() => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton( + () => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + // ViewModels + // Global/shared VMs → LazySingleton + getIt.registerLazySingleton( + () => AuthenticationViewModel( + authenticationRepo: getIt(), + dialogService: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + ), + ); + + // Screen-specific VMs → Factory + // getIt.registerFactory( + // () => BookAppointmentsViewModel( + // bookAppointmentsRepo: getIt(), + // dialogService: getIt(), + // errorHandlerService: getIt(), + // ), + // ); } } diff --git a/lib/core/exceptions/api_failure.dart b/lib/core/exceptions/api_failure.dart index eaa434e..d2a510f 100644 --- a/lib/core/exceptions/api_failure.dart +++ b/lib/core/exceptions/api_failure.dart @@ -1,8 +1,8 @@ - import 'package:equatable/equatable.dart'; abstract class Failure extends Equatable implements Exception { final String message; + const Failure(this.message); } @@ -27,15 +27,30 @@ class LocalStorageFailure extends Failure { List get props => [message]; } +class DataParsingFailure extends Failure { + const DataParsingFailure(super.message); + + @override + List get props => [message]; +} + +class UnknownFailure extends Failure { + const UnknownFailure(super.message); + + @override + List get props => [message]; +} + + class DuplicateUsername extends Failure { - const DuplicateUsername({String? message}) : super(message ?? ''); + const DuplicateUsername(String? message) : super(message ?? ''); @override List get props => [message]; } class InvalidCredentials extends Failure { - const InvalidCredentials({String? message}) : super(message ?? ''); + const InvalidCredentials(String? message) : super(message ?? ''); @override List get props => [message]; diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index d9716a9..c8e69af 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -1,41 +1,40 @@ import 'dart:io'; +import 'package:geolocator/geolocator.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart'; - +import 'package:hmg_patient_app_new/services/navigation_service.dart'; // import 'package:huawei_location/huawei_location.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:provider/provider.dart'; class LocationUtils { + NavigationService navigationService; bool isShowConfirmDialog; bool isShowLocationTimeoutDialog; - BuildContext context; bool isHuawei; final GeolocatorPlatform _geolocatorPlatform = GeolocatorPlatform.instance; - LocationUtils({required this.isShowConfirmDialog, required this.context, this.isHuawei = false, this.isShowLocationTimeoutDialog = true}); + LocationUtils({ + required this.isShowConfirmDialog, + required this.navigationService, + this.isHuawei = false, + this.isShowLocationTimeoutDialog = true, + }); + AppState appState = getIt.get(); void getCurrentLocation({Function(LatLng)? callBack}) async { Geolocator.isLocationServiceEnabled().then((value) async { if (value) { await Geolocator.checkPermission().then((permission) async { if (permission == LocationPermission.always || permission == LocationPermission.whileInUse) { - // Geolocator.getCurrentPosition(locationSettings: LocationSettings(accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5))).then((value) { Geolocator.getLastKnownPosition().then((value) { setLocation(value); if (callBack != null) callBack(LatLng(value?.latitude ?? 24.7101433, value?.longitude ?? 46.6757709)); }).catchError((err) { - print(err); - if (isShowConfirmDialog && isShowLocationTimeoutDialog) { - // showLocationTimeOutDialog(failureCallBack: () { - // Geolocator.openAppSettings(); - // }); - } + if (isShowConfirmDialog && isShowLocationTimeoutDialog) {} }); } @@ -62,15 +61,11 @@ class LocationUtils { } } } - }).catchError((err) { - print(err); - }); + }).catchError((err) {}); } else { if (isShowConfirmDialog) showErrorLocationDialog(false, failureCallBack: () {}); } - }).catchError((err) { - print(err); - }); + }).catchError((err) {}); } Future checkIfGPSIsEnabled() async { @@ -151,8 +146,8 @@ class LocationUtils { Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, position?.latitude ?? 0.0); Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, position?.longitude ?? 0.0); - AppState().setUserLat = position?.latitude ?? 0.0; - AppState().setUserLong = position?.longitude ?? 0.0; + appState.setUserLat = position?.latitude ?? 0.0; + appState.setUserLong = position?.longitude ?? 0.0; // projectViewModel.setLatitudeLongitude(position?.latitude ?? 0.0, position?.longitude ?? 0.0); } @@ -161,8 +156,8 @@ class LocationUtils { Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, 0.0); Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, 0.0); - AppState().setUserLat = 0.0; - AppState().setUserLong = 0.0; + appState.setUserLat = 0.0; + appState.setUserLong = 0.0; } Future requestPermissions() async { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 4f7b398..14e91f8 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -1,24 +1,28 @@ import 'dart:convert'; -import 'package:crypto/crypto.dart' as crypto; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:crypto/crypto.dart' as crypto; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/main.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart'; import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; class Utils { + static AppState appState = getIt.get(); + static NavigationService navigationService = getIt.get(); + static bool _isLoadingVisible = false; static bool get isLoading => _isLoadingVisible; @@ -47,14 +51,16 @@ class Utils { } static String getFreeSlotsTimeText(String startTime, {bool isAddHours = false}) { - // return DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime)!.add( + // return DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime)!.add( // Duration( // hours: isAddHours ? 3 : 0, // ), // )); return !isAddHours - ? DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) - : DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( + ? DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) + : DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( Duration( hours: isAddHours ? 3 : 0, ), @@ -82,12 +88,9 @@ class Utils { } static String getMonthDayYearDateFormatted(DateTime dateTime) { - if (dateTime != null) - return AppState().isArabic() - ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() - : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); - else - return ""; + return appState.isArabic() + ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() + : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); } /// get month by @@ -200,7 +203,7 @@ class Utils { static void showLoadingDialog() { _isLoadingVisible = true; showDialog( - context: navigatorKey.currentContext!, + context: navigationService.navigatorKey.currentContext!, barrierColor: Colors.black.withOpacity(0.5), builder: (BuildContext context) => LoadingDialog(), ) @@ -217,7 +220,7 @@ class Utils { try { if (_isLoadingVisible) { _isLoadingVisible = false; - Navigator.of(navigatorKey.currentContext!).pop(); + Navigator.of(navigationService.navigatorKey.currentContext!).pop(); } _isLoadingVisible = false; } catch (e) {} @@ -242,9 +245,6 @@ class Utils { static bool isSAUDIIDValid(String id, type) { if (type == 1) { - if (id == null) { - return false; - } try { id = id.toString(); id = id.trim(); @@ -304,13 +304,15 @@ class Utils { } static String removeHtmlTags(String htmlString) { - if (htmlString == null || htmlString.isEmpty) { + if (htmlString.isEmpty) { return ''; } // Replace HTML line breaks with newlines - var withLineBreaks = - htmlString.replaceAll(RegExp(r'', multiLine: true), '\n').replaceAll(RegExp(r'<\/p>', multiLine: true), '\n').replaceAll(RegExp(r'', multiLine: true), '\n'); + var withLineBreaks = htmlString + .replaceAll(RegExp(r'', multiLine: true), '\n') + .replaceAll(RegExp(r'<\/p>', multiLine: true), '\n') + .replaceAll(RegExp(r'', multiLine: true), '\n'); // Remove all other HTML tags var withoutTags = withLineBreaks.replaceAll(RegExp(r'<[^>]*>'), ''); @@ -376,7 +378,20 @@ class Utils { final year = parts[0]; // Map month number to short month name (Hijri months) - const hijriMonthNames = ['Muharram', 'Safar', 'Rabi I', 'Rabi II', 'Jumada I', 'Jumada II', 'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah']; + const hijriMonthNames = [ + 'Muharram', + 'Safar', + 'Rabi I', + 'Rabi II', + 'Jumada I', + 'Jumada II', + 'Rajab', + 'Sha\'ban', + 'Ramadan', + 'Shawwal', + 'Dhu al-Qi\'dah', + 'Dhu al-Hijjah' + ]; final monthIndex = int.tryParse(parts[1]) ?? 1; final month = hijriMonthNames[monthIndex - 1]; @@ -458,13 +473,8 @@ class Utils { ); } - - - static Future isGoogleServicesAvailable() async { - GooglePlayServicesAvailability availability = await GoogleApiAvailability - .instance - .checkGooglePlayServicesAvailability(); + GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); String status = availability.toString().split('.').last; if (status == "success") { return true; @@ -472,26 +482,17 @@ class Utils { return false; } - - - - - static Future checkConnection( - {bool bypassConnectionCheck = false}) async { + static Future checkConnection({bool bypassConnectionCheck = false}) async { if (bypassConnectionCheck) return true; - List connectivityResult = - await (Connectivity().checkConnectivity()); - if (connectivityResult.contains(ConnectivityResult.mobile) || - connectivityResult.contains(ConnectivityResult.wifi)) { + List connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult.contains(ConnectivityResult.mobile) || connectivityResult.contains(ConnectivityResult.wifi)) { return true; } else { return false; } } - static String generateMd5Hash(String input) { return crypto.md5.convert(utf8.encode(input)).toString(); } - } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index e544638..04e3a9e 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,16 +1,15 @@ import 'dart:async'; -import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; -import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class AuthenticationRepo { - Future> selectDeviceByImei({required String firebaseToken}); + Future>> selectDeviceByImei({required String firebaseToken}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -20,38 +19,43 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future> selectDeviceByImei({ + Future>> selectDeviceByImei({ required String firebaseToken, }) async { final mapDevice = {"IMEI": firebaseToken}; try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.selectDeviceImei, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['Patient_SELECTDeviceIMEIbyIMEIList'] as List?; + if (list == null || list.isEmpty) { + throw Exception("Device list is empty"); + } - final completer = Completer>(); - await apiClient.post( - ApiConsts.selectDeviceImei, - body: mapDevice, - onSuccess: (response, statusCode) { - try { - final SelectDeviceByImeiRespModelElement model = - SelectDeviceByImeiRespModelElement.fromJson(response['Patient_SELECTDeviceIMEIbyIMEIList'][0]); - completer.complete(Right(model)); - } catch (e) { - completer.complete(Left(ServerFailure(e.toString()))); - } - }, - onFailure: (error, statusCode) { - loggerService.logInfo(("$error - $statusCode").toString()); - completer.complete(Left(ServerFailure(error))); - }, - ); - - return await completer.future; - } on APIException catch (e) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(e.message)); + final model = SelectDeviceByImeiRespModelElement.fromJson(list[0] as Map); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: model, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); } catch (e) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(e.toString())); + return Left(UnknownFailure(e.toString())); } } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8269c72..eaf3c95 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,46 +1,37 @@ -import 'dart:developer'; -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/check_activation_code_request_register.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class AuthenticationViewModel extends ChangeNotifier { AuthenticationRepo authenticationRepo; AppState appState; + ErrorHandlerService errorHandlerService; + DialogService dialogService; AuthenticationViewModel({ required this.appState, required this.authenticationRepo, + required this.errorHandlerService, + required this.dialogService, }); final TextEditingController nationalIdController = TextEditingController(); final TextEditingController phoneNumberController = TextEditingController(); - Future selectDeviceImei({ - Function(dynamic)? onSuccess, - Function(String)? onError - }) async { - final String firebaseToken = - "cIkkB7h7Q7uoFkC4Qv82xG:APA91bEb53Z9XzqymCIctaLxCoMX6bm9fuKlWILQ59uUqfwhCoD42AOP1-jWGB1WYd9BVN5PT2pUUFxrT07vcNg1KH9OH39mrPgCl0m21XVIgWrzNnCkufg"; - - final resultEither = - await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); - - resultEither.fold( - (failure) { - notifyListeners(); - if (onError != null) onError(failure.message); - }, - (data) { - - log("resultEither: ${data.toString()} "); - - notifyListeners(); - if (onSuccess != null) onSuccess(data); + Future selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async { + String firebaseToken = + "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; + final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); + result.fold( + (failure) async => await errorHandlerService.handleError(failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + dialogService.showErrorDialog(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + // move to next api call + } }, ); } diff --git a/lib/features/authentication/models/select_device_by_imei.dart b/lib/features/authentication/models/select_device_by_imei.dart index 2ab669e..398f791 100644 --- a/lib/features/authentication/models/select_device_by_imei.dart +++ b/lib/features/authentication/models/select_device_by_imei.dart @@ -1,77 +1,73 @@ -// To parse this JSON data, do -// -// final selectDeviceByImeiRespModel = selectDeviceByImeiRespModelFromJson(jsonString); - import 'dart:convert'; -Map selectDeviceByImeiRespModelFromJson(String str) => Map.from(json.decode(str)).map((k, v) => MapEntry(k, v)); +Map selectDeviceByImeiRespModelFromJson(String str) => Map.from(json.decode(str)); -String selectDeviceByImeiRespModelToJson(Map data) => json.encode(Map.from(data).map((k, v) => MapEntry(k, v))); +String selectDeviceByImeiRespModelToJson(Map data) => json.encode(Map.from(data)); class SelectDeviceByImeiRespModelElement { - int id; - String imei; - int logInType; - int patientId; - bool outSa; - String mobile; - String identificationNo; - String name; - String nameN; - String createdOn; - String editedOn; - bool biometricEnabled; - int patientType; - int preferredLanguage; + int? id; + String? imei; + int? logInType; + int? patientId; + bool? outSa; + String? mobile; + String? identificationNo; + String? name; + String? nameN; + String? createdOn; + String? editedOn; + bool? biometricEnabled; + int? patientType; + int? preferredLanguage; SelectDeviceByImeiRespModelElement({ - required this.id, - required this.imei, - required this.logInType, - required this.patientId, - required this.outSa, - required this.mobile, - required this.identificationNo, - required this.name, - required this.nameN, - required this.createdOn, - required this.editedOn, - required this.biometricEnabled, - required this.patientType, - required this.preferredLanguage, + this.id, + this.imei, + this.logInType, + this.patientId, + this.outSa, + this.mobile, + this.identificationNo, + this.name, + this.nameN, + this.createdOn, + this.editedOn, + this.biometricEnabled, + this.patientType, + this.preferredLanguage, }); factory SelectDeviceByImeiRespModelElement.fromJson(Map json) => SelectDeviceByImeiRespModelElement( - id: json["ID"], - imei: json["IMEI"], - logInType: json["LogInType"], - patientId: json["PatientID"], - outSa: json["OutSA"], - mobile: json["Mobile"], - identificationNo: json["IdentificationNo"], - name: json["Name"], - nameN: json["NameN"], - createdOn: json["CreatedOn"], - editedOn: json["EditedOn"], - biometricEnabled: json["BiometricEnabled"], - patientType: json["PatientType"], - preferredLanguage: json["PreferredLanguage"], - ); + id: json["ID"] as int?, + imei: json["IMEI"] as String?, + logInType: json["LogInType"] as int?, + patientId: json["PatientID"] as int?, + outSa: json["OutSA"] as bool?, + mobile: json["Mobile"] as String?, + identificationNo: json["IdentificationNo"] as String?, + name: json["Name"] as String?, + nameN: json["NameN"] as String?, + createdOn: json["CreatedOn"] as String?, + editedOn: json["EditedOn"] as String?, + biometricEnabled: json["BiometricEnabled"] as bool?, + patientType: json["PatientType"] as int?, + preferredLanguage: json["PreferredLanguage"] as int?, + ); Map toJson() => { - "ID": id, - "IMEI": imei, - "LogInType": logInType, - "PatientID": patientId, - "OutSA": outSa, - "Mobile": mobile, - "IdentificationNo": identificationNo, - "Name": name, - "NameN": nameN, - "CreatedOn": createdOn, - "EditedOn": editedOn, - "BiometricEnabled": biometricEnabled, - "PatientType": patientType, - "PreferredLanguage": preferredLanguage, - }; + "ID": id, + "IMEI": imei, + "LogInType": logInType, + "PatientID": patientId, + "OutSA": outSa, + "Mobile": mobile, + "IdentificationNo": identificationNo, + "Name": name, + "NameN": nameN, + "CreatedOn": createdOn, + "EditedOn": editedOn, + "BiometricEnabled": biometricEnabled, + "PatientType": patientType, + "PreferredLanguage": preferredLanguage, + }; } diff --git a/lib/main.dart b/lib/main.dart index 3d25c0a..bb4332b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,16 +10,14 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; + import 'core/utils/size_utils.dart'; import 'firebase_options.dart'; -var globalMessengerKey = GlobalKey(); -final navigatorKey = GlobalKey(); - - @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); @@ -57,7 +55,12 @@ void main() async { create: (_) => BottomNavigationProvider(), ), ChangeNotifierProvider( - create: (_) => AuthenticationViewModel(authenticationRepo: getIt(), appState: getIt()), + create: (_) => AuthenticationViewModel( + authenticationRepo: getIt(), + appState: getIt(), + dialogService: getIt(), + errorHandlerService: getIt(), + ), ), ], child: MyApp()), ), @@ -94,7 +97,7 @@ class MyApp extends StatelessWidget { initialRoute: AppRoutes.initialRoute, routes: AppRoutes.routes, theme: AppTheme.getTheme(EasyLocalization.of(context)?.locale.languageCode == "ar"), - navigatorKey: navigatorKey, + navigatorKey: getIt.get().navigatorKey, ); }, ); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index feb7f69..22355cc 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -3,11 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -25,9 +27,10 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { - @override Widget build(BuildContext context) { + AppState appState = getIt.get(); + final AuthenticationViewModel authenticationViewModel = context.read(); return Consumer(builder: (context, navigationProvider, child) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -43,10 +46,8 @@ class _LandingPageState extends State { children: [ CustomButton( text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), - ); + onPressed: () async { + await authenticationViewModel.selectDeviceImei(); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), @@ -66,7 +67,7 @@ class _LandingPageState extends State { ), ), SizedBox(height: 16.h), - AppState().isAuthenticated + appState.isAuthenticated ? Column( children: [ Container( @@ -257,7 +258,7 @@ class _LandingPageState extends State { ), ), SizedBox(height: 16.h), - AppState().isAuthenticated + appState.isAuthenticated ? Column( children: [ Row( diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart new file mode 100644 index 0000000..e931568 --- /dev/null +++ b/lib/services/dialog_service.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +abstract class DialogService { + Future showErrorDialog(String message); +} + +class DialogServiceImp implements DialogService { + final NavigationService navigationService; + + DialogServiceImp({required this.navigationService}); + + @override + Future showErrorDialog(String message) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + + await showModalBottomSheet( + context: context, + isScrollControlled: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (_) => _ErrorBottomSheet(message: message), + ); + } +} + +class _ErrorBottomSheet extends StatelessWidget { + final String message; + + const _ErrorBottomSheet({required this.message}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.error_outline, color: Colors.red, size: 40), + const SizedBox(height: 12), + Text( + "Error", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text("OK"), + ), + ], + ), + ); + } +} diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart new file mode 100644 index 0000000..d8d473a --- /dev/null +++ b/lib/services/error_handler_service.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +abstract class ErrorHandlerService { + Future handleError(Failure failure); +} + +class ErrorHandlerServiceImp implements ErrorHandlerService { + final DialogService dialogService; + final LoggerService loggerService; + final NavigationService navigationService; + + ErrorHandlerServiceImp({ + required this.dialogService, + required this.loggerService, + required this.navigationService, + }); + + @override + Future handleError(Failure failure) async { + if (failure is APIException) { + loggerService.errorLogs("API Exception: ${failure.message}"); + } else if (failure is ServerFailure) { + loggerService.errorLogs("Server Failure: ${failure.message}"); + await _showDialog(failure); + } else if (failure is DataParsingFailure) { + loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); + await _showDialog(failure, title: "Data Error"); + } else if (failure is HttpException) { + loggerService.errorLogs("Http Exception: ${failure.message}"); + await _showDialog(failure, title: "Network Error"); + } else if (failure is UnknownFailure) { + loggerService.errorLogs("Unknown Failure: ${failure.message}"); + await _showDialog(failure, title: "Unknown Error"); + } else if (failure is InvalidCredentials) { + loggerService.errorLogs("Invalid Credentials : ${failure.message}"); + await _showDialog(failure, title: "Unknown Error"); + } else { + loggerService.errorLogs("Unhandled failure type: $failure"); + await _showDialog(failure, title: "Error"); + } + } + + Future _showDialog(Failure failure, {String title = "Error"}) async { + await dialogService.showErrorDialog(failure.message); + } +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart new file mode 100644 index 0000000..d814cf5 --- /dev/null +++ b/lib/services/navigation_service.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + final GlobalKey navigatorKey = GlobalKey(); + + BuildContext? get context => navigatorKey.currentContext; + + Future push(Route route) { + return navigatorKey.currentState!.push(route); + } + + void pop([T? result]) { + navigatorKey.currentState!.pop(result); + } +}