diff --git a/assets/images/svg/doctor_profile_schedule_icon.svg b/assets/images/svg/doctor_profile_schedule_icon.svg
new file mode 100644
index 00000000..20e36299
--- /dev/null
+++ b/assets/images/svg/doctor_profile_schedule_icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json
index d39e8bd7..b69827d4 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -1958,5 +1958,8 @@
"invoiceHistory": "سجل الفواتير",
"refund": "استرداد",
"parkingQR": "رمز الاستجابة السريعة لوقوف السيارات",
- "parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:"
+ "parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:",
+ "livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.",
+ "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟",
+ "schedule": "جدول"
}
diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json
index 61a6da03..bb5b5fe4 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -1947,7 +1947,10 @@
"amountOnly": "Amount",
"invoiceHistory": "Invoice History",
"parkingQR": "Parking QR Code",
- "parkingQRDesc": "Scan the below QR Code at the exit parking gate:"
+ "parkingQRDesc": "Scan the below QR Code at the exit parking gate:",
+ "livecareUpdateInsuranceSent": "Your insurance update request is being processed. You will be notified once the update is completed.",
+ "livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?",
+ "schedule": "Schedule"
}
diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart
index 0c8a2314..6a44cb0e 100644
--- a/lib/core/api/api_client.dart
+++ b/lib/core/api/api_client.dart
@@ -208,10 +208,10 @@ class ApiClientImp implements ApiClient {
if (url == 'https://uat.hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo') {
url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
body['TokenID'] = "@dm!n";
- }
+ }
- body['TokenID'] = "@dm!n";
- // body['PatientID'] = 4774598;
+ // body['TokenID'] = "@dm!n";
+ // body['PatientID'] = 4769748;
// body['PatientID'] = 89754444;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;
@@ -266,29 +266,77 @@ class ApiClientImp implements ApiClient {
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "SocketException: $e");
+ // Enhanced Clarity logging for network errors
+ Clarity.setCustomTag('api_err_type', 'socket_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
- // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody));
- Clarity.sendCustomEvent('api_failure');
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'POST');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_socket_exception');
return;
} on http.ClientException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
+
+ // Enhanced Clarity logging
+ Clarity.setCustomTag('api_err_type', 'client_exception');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'POST');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_client_exception');
+
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "TimeoutException");
+ // Enhanced Clarity logging for timeouts
+ Clarity.setCustomTag('api_err_type', 'timeout');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
- // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody));
- Clarity.sendCustomEvent('api_timeout_failure');
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'POST');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_timeout');
+
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
+
+ // Enhanced Clarity logging for unknown errors
+ Clarity.setCustomTag('api_err_type', 'unknown_exception');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'POST');
+
+ // Truncate exception message for Clarity
+ final exceptionMsg = e.toString();
+ final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
+ Clarity.setCustomTag('api_err_exception', truncatedMsg);
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_unknown_exception');
+
return;
}
@@ -489,21 +537,74 @@ class ApiClientImp implements ApiClient {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "SocketException: $e");
+
+ // Clarity logging for GET requests
+ Clarity.setCustomTag('api_err_type', 'socket_exception');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'GET');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_get_socket_exception');
+
return;
} on http.ClientException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
+
+ Clarity.setCustomTag('api_err_type', 'client_exception');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'GET');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_get_client_exception');
+
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "TimeoutException");
+
+ Clarity.setCustomTag('api_err_type', 'timeout');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'GET');
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_get_timeout');
+
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
+
+ Clarity.setCustomTag('api_err_type', 'unknown_exception');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'GET');
+
+ final exceptionMsg = e.toString();
+ final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
+ Clarity.setCustomTag('api_err_exception', truncatedMsg);
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_get_unknown_exception');
+
return;
}
@@ -513,6 +614,19 @@ class ApiClientImp implements ApiClient {
if (statusCode < 200 || statusCode >= 400) {
onFailure(LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
+
+ // Clarity logging for GET status code errors
+ Clarity.setCustomTag('api_err_type', 'status_code_error');
+ Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
+ Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
+ Clarity.setCustomTag('api_err_method', 'GET');
+ Clarity.setCustomTag('api_err_status', statusCode.toString());
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('api_get_status_error');
} else {
var parsed = json.decode(utf8.decode(response.bodyBytes));
onSuccess(parsed, statusCode);
@@ -695,6 +809,15 @@ class ApiClientImp implements ApiClient {
return '...${url.substring(url.length - (clarityLimit - 3))}';
}
+ String _getEndpointName(String endpoint) {
+ // Extract the last part of the endpoint path for cleaner tracking
+ const int clarityLimit = 100;
+ final cleanEndpoint = endpoint.split('/').last;
+
+ if (cleanEndpoint.length <= clarityLimit) return cleanEndpoint;
+ return '...${cleanEndpoint.substring(cleanEndpoint.length - (clarityLimit - 3))}';
+ }
+
String _sanitizeRequestBody(String body) {
if (body.isEmpty) return '';
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index aa51a5da..664432fd 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
- static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
+ static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@@ -622,6 +622,8 @@ var GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime';
var ADD_NEW_CALL_FOR_PATIENT_ER = 'Services/DoctorApplication.svc/REST/NewCallForPatientER';
+var ADD_LIVECARE_INSURANCE_UPDATE_REQUEST = 'Services/ER_VirtualCall.svc/REST/ER_InsertRequests';
+
var GET_LIVECARE_HISTORY = 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory';
var CANCEL_LIVECARE_REQUEST = 'Services/ER_VirtualCall.svc/REST/DeleteErRequest';
var SEND_LIVECARE_INVOICE_EMAIL = 'Services/Notifications.svc/REST/SendInvoiceForLiveCare';
@@ -986,7 +988,10 @@ var IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE = 'Services/OUTPs.svc/REST/HIS_IsDo
//PAYFORT
var getPayFortProjectDetails = "Services/PayFort_Serv.svc/REST/GetPayFortProjectDetails";
-var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse";
+
+var ADD_PAYFORT_REQUEST = "Services/PayFort_Serv.svc/REST/AddRequest";
+var ADD_PAYFORT_RESPONSE = "Services/PayFort_Serv.svc/REST/AddResponse";
+
// var payFortEnvironment = FortEnvironment.production;
// var applePayMerchantId = "merchant.com.hmgwebservices";
// var payFortEnvironment = FortEnvironment.test;
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 2e27d633..aba22eb3 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -193,6 +193,7 @@ class AppAssets {
static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg';
static const String doctor_profile_rating_icon = '$svgBasePath/doctor_profile_rating_icon.svg';
static const String doctor_profile_reviews_icon = '$svgBasePath/doctor_profile_reviews_icon.svg';
+ static const String doctor_profile_schedule_icon = '$svgBasePath/doctor_profile_schedule_icon.svg';
static const String bookmark_icon = '$svgBasePath/bookmark_icon.svg';
static const String bookmark_filled_icon = '$svgBasePath/bookmark_filled_icon.svg';
static const String waiting_appointment_icon = '$svgBasePath/waitingAppo.svg';
diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart
index 3369ef49..2731cc1a 100644
--- a/lib/core/app_state.dart
+++ b/lib/core/app_state.dart
@@ -47,17 +47,16 @@ class AppState {
AuthenticatedUser? _authenticatedChildUser;
bool isPaytabsEnabled = false;
-
bool isPayfortHostedPageEnabled = true;
void setIsPaytabsEnabled(bool value) {
- // isPaytabsEnabled = value;
- isPaytabsEnabled = false;
+ isPaytabsEnabled = value;
+ // isPaytabsEnabled = true;
}
void setIsPayfortHostedPageEnabled(bool value) {
isPayfortHostedPageEnabled = value;
- // isPayfortHostedPageEnabled = false;
+ // isPayfortHostedPageEnabled = true;
}
int? _superUserID;
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 3c6e923e..c4f7402c 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -75,6 +75,7 @@ import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
+import 'package:hmg_patient_app_new/services/clarity_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/firebase_service.dart';
@@ -366,5 +367,9 @@ class AppDependencies {
));
getIt.registerLazySingleton(() => PayTabsViewModel(appState: getIt()));
+
+ getIt.registerLazySingleton(
+ () => ClarityService(appState: getIt.get()),
+ );
}
}
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index 9e8a044d..320bd1cf 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -4,12 +4,14 @@ import 'dart:io';
import 'dart:math' as dartMath;
import 'dart:typed_data';
+import 'package:clarity_flutter/clarity_flutter.dart';
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:get_it/get_it.dart';
import 'package:google_api_availability/google_api_availability.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@@ -916,7 +918,8 @@ class Utils {
appState.isArabic()
? Container()
: Utils.buildSvgWithAssets(
- icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
+ // icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
+ icon: (getIt.get().getAuthenticatedUser()!.outSa == 0) ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
iconColor: iconColor,
width: iconSize.h,
height: iconSize.h,
@@ -928,7 +931,8 @@ class Utils {
!appState.isArabic()
? Container()
: Utils.buildSvgWithAssets(
- icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
+ // icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
+ icon: (getIt.get().getAuthenticatedUser()!.outSa == 0) ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol,
iconColor: iconColor,
width: iconSize.h,
height: iconSize.h,
@@ -958,7 +962,7 @@ class Utils {
WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
- child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: resolvedIconColor),
+ child: Utils.buildSvgWithAssets(icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: resolvedIconColor),
),
TextSpan(
// text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount),
diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart
index 372f7e8f..1b5c80d1 100644
--- a/lib/features/authentication/widgets/otp_verification_screen.dart
+++ b/lib/features/authentication/widgets/otp_verification_screen.dart
@@ -451,6 +451,7 @@ class _OTPVerificationScreenState extends State {
@override
void initState() {
+ Clarity.setCurrentScreenName('OTP Verification Screen');
super.initState();
_otpController = TextEditingController();
_startResendTimer();
diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart
index 200b51de..e34e2188 100644
--- a/lib/features/book_appointments/book_appointments_view_model.dart
+++ b/lib/features/book_appointments/book_appointments_view_model.dart
@@ -844,7 +844,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
Future getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
docFreeSlots.clear();
DateTime date;
- final DateFormat formatter = DateFormat('HH:mm');
+ final DateFormat formatter = DateFormat('HH:mm', "en-US");
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map _eventsParsed;
@@ -1127,7 +1127,9 @@ class BookAppointmentsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
- print(failure);
+ if(onError != null) {
+ onError(failure.message);
+ }
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart
index 3bee29de..cc87bb7b 100644
--- a/lib/features/habib_wallet/habib_wallet_view_model.dart
+++ b/lib/features/habib_wallet/habib_wallet_view_model.dart
@@ -335,7 +335,6 @@ class HabibWalletViewModel extends ChangeNotifier {
walletRechargeAmount = 0;
selectedRechargeType = 1;
advancePaymentHospitals.clear();
- habibWalletBalanceList.clear();
selectedHospital = null;
fileNumber = '';
depositorName = '';
@@ -417,6 +416,7 @@ class HabibWalletViewModel extends ChangeNotifier {
if (!isWalletAmountToBeLoaded) {
return;
}
+ habibWalletBalanceList.clear();
isWalletAmountLoading = true;
notifyListeners();
diff --git a/lib/features/immediate_livecare/immediate_livecare_repo.dart b/lib/features/immediate_livecare/immediate_livecare_repo.dart
index 8775378b..6726a09b 100644
--- a/lib/features/immediate_livecare/immediate_livecare_repo.dart
+++ b/lib/features/immediate_livecare/immediate_livecare_repo.dart
@@ -25,6 +25,9 @@ abstract class ImmediateLiveCareRepo {
{Function(dynamic)? onSuccess, Function(String)? onError});
Future>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError});
+
+ Future>> addInsuranceUpdateRequestForLiveCare(String name, String mobileNumber, String fileNumber,
+ {Function(dynamic)? onSuccess, Function(String)? onError});
}
class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo {
@@ -250,4 +253,44 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo {
return Left(UnknownFailure(e.toString()));
}
}
+
+ @override
+ Future> addInsuranceUpdateRequestForLiveCare(String name, String mobileNumber, String fileNumber,
+ {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async {
+ Map mapDevice = {
+ "PatientName": name,
+ "MobileNumber": mobileNumber,
+ "FileNumber": fileNumber,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ ADD_LIVECARE_INSURANCE_UPDATE_REQUEST,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ // onError!(error);
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: response,
+ );
+ } 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) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
}
diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart
index 68ae6952..84a79c02 100644
--- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart
+++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart
@@ -121,8 +121,8 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
}
Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result =
- await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!);
+ final result = await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees(
+ _appState.getAuthenticatedUser()!.age ?? 22, _appState.getAuthenticatedUser()!.gender ?? 1, immediateLiveCareSelectedClinic.serviceID ?? 1);
result.fold(
(failure) async {
@@ -245,6 +245,28 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
);
}
+ Future addInsuranceUpdateRequestForLiveCare({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await immediateLiveCareRepo.addInsuranceUpdateRequestForLiveCare("${_appState.getAuthenticatedUser()?.firstName ?? ''} ${_appState.getAuthenticatedUser()?.lastName ?? ''}",
+ _appState.getAuthenticatedUser()!.mobileNumber ?? "", _appState.getAuthenticatedUser()!.patientId.toString() ?? "");
+
+ result.fold(
+ (failure) async {
+ onError!(failure.message);
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ onError!(apiResponse.errorMessage ?? "Unknown error occurred");
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
Future getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async {
patientLiveCareHistoryList = [];
patientHasPendingLiveCareRequest = false;
diff --git a/lib/features/my_appointments/appointment_rating_view_model.dart b/lib/features/my_appointments/appointment_rating_view_model.dart
index 6dd41ff5..cef25419 100644
--- a/lib/features/my_appointments/appointment_rating_view_model.dart
+++ b/lib/features/my_appointments/appointment_rating_view_model.dart
@@ -25,6 +25,7 @@ class AppointmentRatingViewModel extends ChangeNotifier {
bool isRateClinic = false;
Future getLastRatingAppointment({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ appointmentRatedList.clear();
final result = await myAppointmentsRepo.getLastRatingAppointment();
result.fold(
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index 90f504d2..9182f9e7 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -588,7 +588,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ // (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async {
+ if(onError != null) {
+ onError(failure.message);
+ }
+ },
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
@@ -1010,7 +1015,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
}
// patientQueueDetailsList.first.callType = 1;
- patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
+ // if (patientQueueDetailsList.length > 1) {
+ patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
+ // }
} else {
patientQueueDetailsList.clear();
isPatientHasQueueAppointment = false;
diff --git a/lib/features/payfort/models/payfort_request_insert_model.dart b/lib/features/payfort/models/payfort_request_insert_model.dart
new file mode 100644
index 00000000..56f354f5
--- /dev/null
+++ b/lib/features/payfort/models/payfort_request_insert_model.dart
@@ -0,0 +1,128 @@
+class PayfortRequestInsertModel {
+ String? accessCode;
+ String? commandType;
+ num? amount;
+ String? customerEmail;
+ String? orderDescription;
+ String? serviceID;
+ String? returnURL;
+ String? paymentOption;
+ String? clientRequestID;
+ String? projectID;
+ String? fileNumber;
+ String? customerName;
+ String? currency;
+ String? liveServiceID;
+ String? longitude;
+ String? latitude;
+ String? merchantReference;
+ String? merchantIdentifier;
+ String? deviceToken;
+ String? sHARequestPhase;
+ String? sHAResponsePhase;
+ String? signature;
+ String? language;
+ String? integrationId;
+ String? iPAdress;
+ String? isSchedule;
+ String? appointmentDate;
+ String? appointmentNo;
+ String? doctorID;
+
+ PayfortRequestInsertModel(
+ {this.accessCode,
+ this.commandType,
+ this.amount,
+ this.customerEmail,
+ this.orderDescription,
+ this.serviceID,
+ this.returnURL,
+ this.paymentOption,
+ this.clientRequestID,
+ this.projectID,
+ this.fileNumber,
+ this.customerName,
+ this.currency,
+ this.liveServiceID,
+ this.longitude,
+ this.latitude,
+ this.merchantReference,
+ this.merchantIdentifier,
+ this.deviceToken,
+ this.sHARequestPhase,
+ this.sHAResponsePhase,
+ this.signature,
+ this.language,
+ this.integrationId,
+ this.iPAdress,
+ this.isSchedule,
+ this.appointmentDate,
+ this.appointmentNo,
+ this.doctorID});
+
+ PayfortRequestInsertModel.fromJson(Map json) {
+ accessCode = json['Access_code'];
+ commandType = json['CommandType'];
+ amount = json['Amount'];
+ customerEmail = json['CustomerEmail'];
+ orderDescription = json['OrderDescription'];
+ serviceID = json['Service_ID'];
+ returnURL = json['ReturnURL'];
+ paymentOption = json['PaymentOption'];
+ clientRequestID = json['ClientRequestID'];
+ projectID = json['ProjectID'];
+ fileNumber = json['FileNumber'];
+ customerName = json['CustomerName'];
+ currency = json['Currency'];
+ liveServiceID = json['Live_ServiceID'];
+ longitude = json['Longitude'];
+ latitude = json['Latitude'];
+ merchantReference = json['Merchant_Reference'];
+ merchantIdentifier = json['Merchant_Identifier'];
+ deviceToken = json['DeviceToken'];
+ sHARequestPhase = json['SHA_RequestPhase'];
+ sHAResponsePhase = json['SHA_ResponsePhase'];
+ signature = json['Signature'];
+ language = json['Language'];
+ integrationId = json['Integration_Id'];
+ iPAdress = json['IPAdress'];
+ isSchedule = json['IsSchedule'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentNo = json['AppointmentNo'];
+ doctorID = json['DoctorID'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['Access_code'] = this.accessCode;
+ data['CommandType'] = this.commandType;
+ data['Amount'] = this.amount;
+ data['CustomerEmail'] = this.customerEmail;
+ data['OrderDescription'] = this.orderDescription;
+ data['Service_ID'] = this.serviceID;
+ data['ReturnURL'] = this.returnURL;
+ data['PaymentOption'] = this.paymentOption;
+ data['ClientRequestID'] = this.clientRequestID;
+ data['ProjectID'] = this.projectID;
+ data['FileNumber'] = this.fileNumber;
+ data['CustomerName'] = this.customerName;
+ data['Currency'] = this.currency;
+ data['Live_ServiceID'] = this.liveServiceID;
+ data['Longitude'] = this.longitude;
+ data['Latitude'] = this.latitude;
+ data['Merchant_Reference'] = this.merchantReference;
+ data['Merchant_Identifier'] = this.merchantIdentifier;
+ data['DeviceToken'] = this.deviceToken;
+ data['SHA_RequestPhase'] = this.sHARequestPhase;
+ data['SHA_ResponsePhase'] = this.sHAResponsePhase;
+ data['Signature'] = this.signature;
+ data['Language'] = this.language;
+ data['Integration_Id'] = this.integrationId;
+ data['IPAdress'] = this.iPAdress;
+ data['IsSchedule'] = this.isSchedule;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['DoctorID'] = this.doctorID;
+ return data;
+ }
+}
diff --git a/lib/features/payfort/models/payfort_response_insert_model.dart b/lib/features/payfort/models/payfort_response_insert_model.dart
new file mode 100644
index 00000000..452ca2a5
--- /dev/null
+++ b/lib/features/payfort/models/payfort_response_insert_model.dart
@@ -0,0 +1,77 @@
+class PayfortResponseInsertModel {
+ String? fortId;
+ String? commandType;
+ num? amount;
+ String? sadadOLP;
+ String? paymentOption;
+ String? customerIP;
+ String? eCI;
+ String? responseMessage;
+ String? cardNumber;
+ String? cardBin;
+ int? installmentsNumber;
+ String? status;
+ String? merchantRef;
+ bool? isRefund;
+ bool? remmeberMe;
+ String? reconciliationReference;
+
+ PayfortResponseInsertModel({
+ this.fortId,
+ this.commandType,
+ this.amount,
+ this.sadadOLP,
+ this.paymentOption,
+ this.customerIP,
+ this.eCI,
+ this.responseMessage,
+ this.cardNumber,
+ this.cardBin,
+ this.installmentsNumber,
+ this.status,
+ this.merchantRef,
+ this.isRefund,
+ this.remmeberMe,
+ this.reconciliationReference,
+ });
+
+ PayfortResponseInsertModel.fromJson(Map json) {
+ fortId = json['Fort_id'];
+ commandType = json['CommandType'];
+ amount = json['Amount'];
+ sadadOLP = json['Sadad_OLP'];
+ paymentOption = json['Payment_Option'];
+ customerIP = json['Customer_IP'];
+ eCI = json['ECI'];
+ responseMessage = json['Response_Message'];
+ cardNumber = json['Card_Number'];
+ cardBin = json['Card_Bin'];
+ installmentsNumber = json['Installments_Number'];
+ status = json['Status'];
+ merchantRef = json['Merchant_Ref'];
+ isRefund = json['IsRefund'];
+ remmeberMe = json['RemmeberMe'];
+ reconciliationReference = json['Reconciliation_Reference'];
+ }
+
+ Map toJson() {
+ final Map data = Map();
+ data['Fort_id'] = this.fortId;
+ data['CommandType'] = this.commandType;
+ data['Amount'] = this.amount;
+ data['Sadad_OLP'] = this.sadadOLP;
+ data['Payment_Option'] = this.paymentOption;
+ data['Customer_IP'] = this.customerIP;
+ data['ECI'] = this.eCI;
+ data['Response_Message'] = this.responseMessage;
+ data['Card_Number'] = this.cardNumber;
+ data['Card_Bin'] = this.cardBin;
+ data['Installments_Number'] = this.installmentsNumber;
+ data['Status'] = this.status;
+ data['Merchant_Ref'] = this.merchantRef;
+ data['IsRefund'] = this.isRefund;
+ data['RemmeberMe'] = this.remmeberMe;
+ data['Reconciliation_Reference'] = this.reconciliationReference;
+ return data;
+ }
+}
diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart
index c00b01c2..3cd27a9d 100644
--- a/lib/features/payfort/payfort_repo.dart
+++ b/lib/features/payfort/payfort_repo.dart
@@ -8,6 +8,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart';
+import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
+import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/sdk_token_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@@ -28,6 +30,10 @@ abstract class PayfortRepo {
{required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID});
Future>> getTamaraInstallmentsDetails();
+
+ Future>> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel});
+
+ Future>> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel});
}
class PayfortRepoImp implements PayfortRepo {
@@ -291,4 +297,57 @@ class PayfortRepoImp implements PayfortRepo {
}
}
+ @override
+ Future> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel}) async {
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(ADD_PAYFORT_REQUEST, body: payfortRequestInsertModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: response,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ }, isAllowAny: true, isPaymentServices: true);
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel}) async {
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(ADD_PAYFORT_RESPONSE, body: payfortResponseInsertModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: response,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ }, isAllowAny: true, isPaymentServices: true);
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
}
diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart
index 8d26eab1..f567264f 100644
--- a/lib/features/payfort/payfort_view_model.dart
+++ b/lib/features/payfort/payfort_view_model.dart
@@ -8,11 +8,14 @@ import 'package:flutter/services.dart';
import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.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/cache_consts.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/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
-import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart';
+import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
+import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/sdk_token_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@@ -20,6 +23,8 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:crypto/crypto.dart';
+import 'models/apple_pay_request_insert_model.dart';
+
class PayfortViewModel extends ChangeNotifier {
PayfortRepo payfortRepo;
ErrorHandlerService errorHandlerService;
@@ -92,6 +97,44 @@ class PayfortViewModel extends ChangeNotifier {
);
}
+ Future payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
+
+ result.fold(
+ (failure) async => await errorHandlerService.handleError(failure: failure),
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ // payfortProjectDetailsRespModel = apiResponse.data!;
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
+
+ result.fold(
+ (failure) async => await errorHandlerService.handleError(failure: failure),
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ // payfortProjectDetailsRespModel = apiResponse.data!;
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
Future checkPaymentStatus({required String transactionID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.checkPaymentStatus(transactionID: transactionID);
@@ -291,13 +334,19 @@ class PayfortViewModel extends ChangeNotifier {
}
Future initiatePaymentWithCard( // var request, {
- {
+ String paymentOption,
+ String liveServiceID,
+ String isScheduleLiveCare,
+ String appointmentDate,
+ String appointmentNo,
+ String doctorID, {
SucceededCallback? onSucceeded,
FailedCallback? onFailed,
String countryIsoCode = "SA",
String transactionID = "",
String amount = "0",
String fileNumber = "0",
+ String orderDescription = "",
}) async {
dynamic paymentResult;
@@ -319,12 +368,44 @@ class PayfortViewModel extends ChangeNotifier {
"amount": Platform.isAndroid ? (num.parse(amount) * 100).toInt() : (num.parse(amount) * 100).toInt().toString(),
// "amount": "100000",
"currency": getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
- "language": "en",
+ "language": getIt.get().isArabic() ? "ar" : "en",
"customer_email": "CustID_$fileNumber@HMG.com",
"sdk_token": sdkTokenResponse!.sdkToken,
"customer_name": "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}",
};
+ PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
+
+ payfortRequestInsertModel.accessCode = payfortProjectDetailsRespModel!.accessCode;
+ payfortRequestInsertModel.commandType = "PURCHASE";
+ payfortRequestInsertModel.amount = num.parse(amount);
+ payfortRequestInsertModel.customerEmail = getIt.get().getAuthenticatedUser()!.emailAddress;
+ payfortRequestInsertModel.orderDescription = orderDescription;
+ payfortRequestInsertModel.serviceID = payfortProjectDetailsRespModel!.servID.toString();
+ payfortRequestInsertModel.paymentOption = paymentOption;
+ payfortRequestInsertModel.clientRequestID = transactionID;
+ payfortRequestInsertModel.projectID = payfortProjectDetailsRespModel!.projectID.toString();
+ payfortRequestInsertModel.fileNumber = getIt.get().getAuthenticatedUser()!.patientId.toString();
+ payfortRequestInsertModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}";
+ payfortRequestInsertModel.currency = getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
+ payfortRequestInsertModel.liveServiceID = liveServiceID;
+ payfortRequestInsertModel.latitude = getIt.get().userLat.toString();
+ payfortRequestInsertModel.longitude = getIt.get().userLong.toString();
+ payfortRequestInsertModel.merchantReference = transactionID;
+ payfortRequestInsertModel.merchantIdentifier = payfortProjectDetailsRespModel!.merchantIdentifier;
+ payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
+ payfortRequestInsertModel.sHARequestPhase = payfortProjectDetailsRespModel!.shaRequest;
+ payfortRequestInsertModel.sHAResponsePhase = payfortProjectDetailsRespModel!.shaResponse;
+ payfortRequestInsertModel.signature = sdkTokenResponse!.signature;
+ payfortRequestInsertModel.language = getIt.get().isArabic() ? "ar" : "en";
+ payfortRequestInsertModel.integrationId = "1";
+ payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
+ payfortRequestInsertModel.appointmentDate = appointmentDate;
+ payfortRequestInsertModel.appointmentNo = appointmentNo;
+ payfortRequestInsertModel.doctorID = doctorID;
+
+ await payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
+
LoaderBottomSheet.hideLoader();
paymentResult = await FlutterAmazonpaymentservices.normalPay(request, ApiConsts.payFortEnvironmentType, isShowResponsePage: false);
@@ -332,6 +413,27 @@ class PayfortViewModel extends ChangeNotifier {
String responseCode = paymentMap['response_code'] ?? "";
print("Payment Success Data: ${paymentMap.toString()}");
+ PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel();
+
+ payfortResponseInsertModel.fortId = paymentMap['fort_id'] ?? "";
+ payfortResponseInsertModel.commandType = paymentMap['command'] ?? "";
+ payfortResponseInsertModel.amount = num.tryParse(paymentMap['amount'] ?? "0") ?? 0;
+ payfortResponseInsertModel.sadadOLP = "";
+ payfortResponseInsertModel.paymentOption = paymentMap['payment_option'] ?? "";
+ payfortResponseInsertModel.customerIP = paymentMap['customer_ip'] ?? "";
+ payfortResponseInsertModel.eCI = paymentMap['eci'] ?? "";
+ payfortResponseInsertModel.responseMessage = paymentMap['response_message'] ?? "";
+ payfortResponseInsertModel.cardNumber = paymentMap['card_number'] ?? "";
+ payfortResponseInsertModel.cardBin = paymentMap['card_bin'] ?? "";
+ payfortResponseInsertModel.installmentsNumber = int.tryParse(paymentMap['installments_number'] ?? "0") ?? 0;
+ payfortResponseInsertModel.status = paymentMap['status'] ?? "";
+ payfortResponseInsertModel.merchantRef = paymentMap['merchant_reference'] ?? "";
+ payfortResponseInsertModel.isRefund = false;
+ payfortResponseInsertModel.remmeberMe = true;
+ payfortResponseInsertModel.reconciliationReference = "";
+
+ await payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
+
if (responseCode.startsWith("14") || responseCode == "20064") {
if (onSucceeded != null) {
onSucceeded(PayFortResult.fromMap(paymentMap));
@@ -353,6 +455,11 @@ class PayfortViewModel extends ChangeNotifier {
// Process real payment rejection errors here
print("Actual Payment Failure: ${e.details.toString()}");
+ if (onFailed != null) {
+ final paymentMap = (e.details as Map).cast();
+ paymentMap["message"] = e.details["response_message"] ?? "Payment failed with unknown error.";
+ onFailed(PayFortFailureResult.fromMap(paymentMap));
+ }
} catch (error) {
print("Generic Error: $error");
}
diff --git a/lib/features/weather/weather_view_model.dart b/lib/features/weather/weather_view_model.dart
index a145fcca..081ef192 100644
--- a/lib/features/weather/weather_view_model.dart
+++ b/lib/features/weather/weather_view_model.dart
@@ -23,10 +23,14 @@ class WeatherMonitorViewModel extends ChangeNotifier {
final List _cityInfoList = [];
bool isLoading = false;
+ bool isWeatherNeedToLoad = true;
List get cityInfoList => _cityInfoList;
Future fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ if (!isWeatherNeedToLoad) {
+ return;
+ }
isLoading = true;
notifyListeners();
_cityInfoList.clear();
@@ -34,7 +38,7 @@ class WeatherMonitorViewModel extends ChangeNotifier {
// Check if location permission is already granted
final isLocationEnabled = await locationUtils.isLocationPermissionEnabled();
- if (isLocationEnabled) {
+ if (isLocationEnabled && getIt.get().userLat != 0.0 && getIt.get().userLong != 0.0) {
// Permission already granted, fetch data directly
isLocationAvailable = true;
final result = await weatherRepo.getCityInfo();
@@ -61,6 +65,7 @@ class WeatherMonitorViewModel extends ChangeNotifier {
if (onSuccess != null) {
onSuccess(apiResponse);
}
+ isWeatherNeedToLoad = false;
}
},
);
diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart
index 8d29d818..c82e2d12 100644
--- a/lib/generated/locale_keys.g.dart
+++ b/lib/generated/locale_keys.g.dart
@@ -1940,5 +1940,8 @@ abstract class LocaleKeys {
static const refund = 'refund';
static const parkingQR = 'parkingQR';
static const parkingQRDesc = 'parkingQRDesc';
+ static const livecareUpdateInsuranceSent = 'livecareUpdateInsuranceSent';
+ static const livecareUpdateInsurance = 'livecareUpdateInsurance';
+ static const schedule = 'schedule';
}
diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart
index daf0839a..4d181230 100644
--- a/lib/presentation/appointments/appointment_details_page.dart
+++ b/lib/presentation/appointments/appointment_details_page.dart
@@ -40,6 +40,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appoin
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
+import 'package:hmg_patient_app_new/services/clarity_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@@ -1215,6 +1216,16 @@ class _AppointmentDetailsPageState extends State {
isDismissible: false,
isFullScreen: false,
isSuccessDialog: true);
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
});
// LoaderBottomSheet.hideLoader();
case 15:
diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart
index 79b99a1e..46130b12 100644
--- a/lib/presentation/appointments/appointment_payment_page.dart
+++ b/lib/presentation/appointments/appointment_payment_page.dart
@@ -69,11 +69,13 @@ class _AppointmentPaymentPageState extends State {
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) {
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
- if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
- myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
- setState(() {
- isShowTamara = true;
- });
+ if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) {
+ if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
+ myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
+ setState(() {
+ isShowTamara = true;
+ });
+ }
}
});
}, onError: (err) {
@@ -226,8 +228,15 @@ class _AppointmentPaymentPageState extends State {
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 1);
payfortViewModel.initiatePaymentWithCard(
+ "mada",
+ "0",
+ widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
+ widget.patientAppointmentHistoryResponseModel.appointmentDate!,
+ widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
+ widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
+ orderDescription: "Appointment Payment",
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
@@ -442,9 +451,16 @@ class _AppointmentPaymentPageState extends State {
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 1);
payfortViewModel.initiatePaymentWithCard(
+ "visa",
+ "0",
+ widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
+ widget.patientAppointmentHistoryResponseModel.appointmentDate!,
+ widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
+ widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
+ orderDescription: "Appointment Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart
index 59522149..f2bd1d18 100644
--- a/lib/presentation/appointments/appointment_queue_page.dart
+++ b/lib/presentation/appointments/appointment_queue_page.dart
@@ -26,6 +26,8 @@ class AppointmentQueuePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
appState = getIt.get();
+ final currentQueue = getIt.get().currentPatientQueueDetails;
+ final currentStatus = getIt.get().currentQueueStatus;
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer(builder: (context, myAppointmentsVM, child) {
@@ -67,7 +69,7 @@ class AppointmentQueuePage extends StatelessWidget {
labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context),
backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20),
textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus),
- ).toShimmer2(isShow: myAppointmentsVM.patientQueueDetailsList.isEmpty),
+ ).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.greyColor,
@@ -88,7 +90,7 @@ class AppointmentQueuePage extends StatelessWidget {
SizedBox(height: 8.h),
(myAppointmentsVM.currentPatientQueueDetails.queueNo ?? "IM-123")
.toText32(isBold: true)
- .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty),
+ .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
SizedBox(height: 8.h),
CustomButton(
text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""),
@@ -103,7 +105,7 @@ class AppointmentQueuePage extends StatelessWidget {
height: 40.h,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
- ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty),
+ ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
],
),
),
diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart
index 817ebfa0..dd6cf8eb 100644
--- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart
+++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart
@@ -10,6 +10,7 @@ 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/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@@ -153,9 +154,15 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
+
+ getIt.get().setIsWalletAmountToBeLoaded(true);
+ getIt.get().initHabibWalletProvider();
+ getIt.get().getPatientBalanceAmount();
+
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async {
// await myAppointmentsViewModel.getPatientAppointmentQueueDetails();
+
Navigator.of(context).pop();
Navigator.pushAndRemoveUntil(
context,
diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
index 59783f20..f4d20944 100644
--- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart
+++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
@@ -60,7 +60,7 @@ class AppointmentDoctorCard extends StatelessWidget {
Column(
children: [
Utils.buildImgWithNetwork(
- url: patientAppointmentHistoryResponseModel.doctorImageURL!,
+ url: patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png",
iconColor: AppColors.transparent,
width: 63.h,
height: 63.h,
diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart
index c38e9ad9..dee44de0 100644
--- a/lib/presentation/authentication/login.dart
+++ b/lib/presentation/authentication/login.dart
@@ -94,27 +94,29 @@ class LoginScreenState extends State {
LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor ),
SizedBox(height: 32.h),
Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget
- TextInputWidget(
- labelText: LocaleKeys.nationalIdFileNumber.tr(context: context),
- hintText: "xxxxxxxxx",
- controller: authVm.nationalIdController,
- focusNode: _nationalIdFocusNode,
- keyboardType: TextInputType.number,
- isEnable: true,
- prefix: null,
- autoFocus: true,
- isAllowRadius: true,
- isBorderAllowed: false,
- isAllowLeadingIcon: true,
- padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h),
- leadingIcon: AppAssets.student_card,
- errorMessage: authVm.nationalIdError,
- hasError: authVm.nationalIdError != null,
- fontFamily: "Poppins",
- onChange: (value) {
- // Clear error when user starts typing
- authVm.clearNationalIdError();
- },
+ ClarityUnmask(
+ child: TextInputWidget(
+ labelText: LocaleKeys.nationalIdFileNumber.tr(context: context),
+ hintText: "xxxxxxxxx",
+ controller: authVm.nationalIdController,
+ focusNode: _nationalIdFocusNode,
+ keyboardType: TextInputType.number,
+ isEnable: true,
+ prefix: null,
+ autoFocus: true,
+ isAllowRadius: true,
+ isBorderAllowed: false,
+ isAllowLeadingIcon: true,
+ padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h),
+ leadingIcon: AppAssets.student_card,
+ errorMessage: authVm.nationalIdError,
+ hasError: authVm.nationalIdError != null,
+ fontFamily: "Poppins",
+ onChange: (value) {
+ // Clear error when user starts typing
+ authVm.clearNationalIdError();
+ },
+ ),
),
SizedBox(height: 16.h),
CustomButton(
@@ -205,65 +207,67 @@ class LoginScreenState extends State {
child: SingleChildScrollView(
child: Consumer(
builder: (context, authVm, child) {
- return GenericBottomSheet(
- countryCode: authVm.selectedCountrySignup.countryCode,
- initialPhoneNumber: "",
- textController: phoneNumberController,
- isEnableCountryDropdown: true,
- onCountryChange: (country) {
- authVm.onCountryChange(country);
- // Clear error when country changes
- authVm.clearPhoneNumberError();
- },
- onChange: (value) {
- authVm.onPhoneNumberChange(value);
- // Clear error when user starts typing
- authVm.clearPhoneNumberError();
- },
- phoneNumberError: authVm.phoneNumberError,
- buttons: [
- if (authVm.selectedCountrySignup != CountryEnum.others)
+ return ClarityUnmask(
+ child: GenericBottomSheet(
+ countryCode: authVm.selectedCountrySignup.countryCode,
+ initialPhoneNumber: "",
+ textController: phoneNumberController,
+ isEnableCountryDropdown: true,
+ onCountryChange: (country) {
+ authVm.onCountryChange(country);
+ // Clear error when country changes
+ authVm.clearPhoneNumberError();
+ },
+ onChange: (value) {
+ authVm.onPhoneNumberChange(value);
+ // Clear error when user starts typing
+ authVm.clearPhoneNumberError();
+ },
+ phoneNumberError: authVm.phoneNumberError,
+ buttons: [
+ if (authVm.selectedCountrySignup != CountryEnum.others)
+ Padding(
+ padding: EdgeInsets.only(bottom: 10.h),
+ child: CustomButton(
+ text: LocaleKeys.sendOTPSMS.tr(context: context),
+ onPressed: () async {
+ validatePhoneAndProceed(OTPTypeEnum.sms);
+ },
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedBorderColor,
+ textColor: Colors.white,
+ iconColor: Colors.white,
+ icon: AppAssets.message,
+ ),
+ ),
+ if (authVm.selectedCountrySignup != CountryEnum.others)
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.h),
+ child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor),
+ ),
+ ],
+ ),
Padding(
- padding: EdgeInsets.only(bottom: 10.h),
+ padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
- text: LocaleKeys.sendOTPSMS.tr(context: context),
+ text: LocaleKeys.sendOTPWHATSAPP.tr(context: context),
onPressed: () async {
- validatePhoneAndProceed(OTPTypeEnum.sms);
+ validatePhoneAndProceed(OTPTypeEnum.whatsapp);
},
- backgroundColor: AppColors.primaryRedColor,
- borderColor: AppColors.primaryRedBorderColor,
- textColor: Colors.white,
- iconColor: Colors.white,
- icon: AppAssets.message,
+ backgroundColor: AppColors.whiteColor,
+ borderColor: AppColors.textColor,
+ textColor: AppColors.textColor,
+ icon: AppAssets.whatsapp,
+ iconColor: null,
+ applyThemeColor: false,
),
),
- if (authVm.selectedCountrySignup != CountryEnum.others)
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.h),
- child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor),
- ),
- ],
- ),
- Padding(
- padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
- child: CustomButton(
- text: LocaleKeys.sendOTPWHATSAPP.tr(context: context),
- onPressed: () async {
- validatePhoneAndProceed(OTPTypeEnum.whatsapp);
- },
- backgroundColor: AppColors.whiteColor,
- borderColor: AppColors.textColor,
- textColor: AppColors.textColor,
- icon: AppAssets.whatsapp,
- iconColor: null,
- applyThemeColor: false,
- ),
- ),
- ],
+ ],
+ ),
);
},
),
diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart
index e77038a5..8d44f377 100644
--- a/lib/presentation/authentication/register.dart
+++ b/lib/presentation/authentication/register.dart
@@ -1,3 +1,4 @@
+import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -31,6 +32,7 @@ class _RegisterNew extends State {
@override
void initState() {
+ Clarity.setCurrentScreenName('Registration Step 1 Screen');
super.initState();
_nationalIdFocusNode = FocusNode();
_dobFocusNode = FocusNode();
diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart
index cb96e63d..46919e95 100644
--- a/lib/presentation/authentication/register_step2.dart
+++ b/lib/presentation/authentication/register_step2.dart
@@ -1,3 +1,4 @@
+import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@@ -34,6 +35,7 @@ class _RegisterNew extends State {
@override
void initState() {
+ Clarity.setCurrentScreenName('Registration Step 2 Screen');
super.initState();
authVM = context.read();
insuranceVM = context.read();
diff --git a/lib/presentation/blood_donation/widgets/select_gender_widget.dart b/lib/presentation/blood_donation/widgets/select_gender_widget.dart
index 1368c4e3..e51d3e78 100644
--- a/lib/presentation/blood_donation/widgets/select_gender_widget.dart
+++ b/lib/presentation/blood_donation/widgets/select_gender_widget.dart
@@ -57,9 +57,12 @@ class SelectGenderWidget extends StatelessWidget {
),
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
- final genderValue = isArabic
- ? GenderTypeEnum.values[index].typeAr
- : GenderTypeEnum.values[index].type;
+ final genderValue =
+ // isArabic
+ // ? GenderTypeEnum.values[index].typeAr
+
+ // :
+ GenderTypeEnum.values[index].type;
bloodDonationViewModel.onGenderChange(genderValue);
Navigator.of(context).pop();
}));
diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart
index be097651..f54f95bf 100644
--- a/lib/presentation/book_appointment/doctor_profile_page.dart
+++ b/lib/presentation/book_appointment/doctor_profile_page.dart
@@ -192,6 +192,15 @@ class DoctorProfilePage extends StatelessWidget {
isCloseButtonVisible: true,
);
}),
+ SizedBox(width: 36.w),
+ Column(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_schedule_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
+ SizedBox(height: 16.h),
+ LocaleKeys.schedule.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
+ LocaleKeys.view.tr(context: context).toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
+ ],
+ ).onPress(() {})
],
),
SizedBox(height: 16.h),
diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart
index 8bbf23af..57fd5e94 100644
--- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart
+++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart
@@ -186,27 +186,64 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
- Navigator.of(context)
- .push(
- CustomPageRoute(
- page: InsuranceHomePage(),
- ),
- )
- .then((val) async {
- LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr());
- await immediateLiveCareVM.getLiveCareImmediateAppointmentFees(onSuccess: (val) {
- LoaderBottomSheet.hideLoader();
- }, onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- });
+ showCommonBottomSheetWithoutHeight(
+ title: LocaleKeys.notice.tr(context: context),
+ context,
+ child: Utils.getWarningWidget(
+ loadingText: LocaleKeys.livecareUpdateInsurance.tr(),
+ isShowActionButtons: true,
+ onCancelTap: () {
+ Navigator.pop(context);
+ },
+ onConfirmTap: () async {
+ Navigator.pop(context);
+ LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr());
+ await immediateLiveCareVM.addInsuranceUpdateRequestForLiveCare(onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getSuccessWidget(loadingText: LocaleKeys.livecareUpdateInsuranceSent.tr()),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ }, onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ }),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+
+ // Navigator.of(context)
+ // .push(
+ // CustomPageRoute(
+ // page: InsuranceHomePage(),
+ // ),
+ // )
+ // .then((val) async {
+ // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr());
+ // await immediateLiveCareVM.getLiveCareImmediateAppointmentFees(onSuccess: (val) {
+ // LoaderBottomSheet.hideLoader();
+ // }, onError: (err) {
+ // LoaderBottomSheet.hideLoader();
+ // showCommonBottomSheetWithoutHeight(
+ // context,
+ // child: Utils.getErrorWidget(loadingText: err),
+ // callBackFunc: () {},
+ // isFullScreen: false,
+ // isCloseButtonVisible: true,
+ // );
+ // });
+ // });
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart
index aa23fef8..972606a9 100644
--- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart
+++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart
@@ -63,11 +63,13 @@ class _ImmediateLiveCarePaymentPageState extends State= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
- num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
- setState(() {
- isShowTamara = true;
- });
+ if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) {
+ if (num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
+ num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
+ setState(() {
+ isShowTamara = true;
+ });
+ }
}
});
});
@@ -182,9 +184,16 @@ class _ImmediateLiveCarePaymentPageState extends State {
if (bookAppointmentsViewModel.isLiveCareSchedule) {
await bookAppointmentsViewModel.insertSpecificAppointmentForLiveCare(onError: (err) {
- print(err.data["ErrorEndUserMessage"]);
LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ title: LocaleKeys.notice.tr(context: context),
+ context,
+ child: Utils.getErrorWidget(loadingText: err.toString()),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
}, onSuccess: (apiResp) async {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart
index bd2b6462..b9d63f55 100644
--- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart
+++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart
@@ -1,11 +1,13 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/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/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart';
@@ -114,11 +116,11 @@ class SelectLivecareClinicPage extends StatelessWidget {
),
),
),
- Column(
- children: [
- CustomButton(
- text: LocaleKeys.yesPleasImInAHurry.tr(context: context),
- onPressed: () async {
+ getIt.get().isAuthenticated
+ ? Column(children: [
+ CustomButton(
+ text: LocaleKeys.yesPleasImInAHurry.tr(context: context),
+ onPressed: () async {
Navigator.pop(context);
GetLiveCareClinicListResponseModel liveCareClinic = GetLiveCareClinicListResponseModel(
iD: selectedClinic.liveCareClinicID,
@@ -177,8 +179,24 @@ class SelectLivecareClinicPage extends StatelessWidget {
height: 50.h,
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
- ],
- ),
+ ])
+ : CustomButton(
+ text: LocaleKeys.loginToUseServiceLiveCare.tr(),
+ onPressed: () async {
+ await getIt.get().onLoginPressed();
+ },
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedColor,
+ textColor: AppColors.whiteColor,
+ fontSize: 16.f,
+ isBold: true,
+ borderRadius: 12.r,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 50.h,
+ icon: AppAssets.login1,
+ iconColor: AppColors.whiteColor,
+ iconSize: 24.h,
+ ).paddingSymmetrical(24.h, 120.h),
],
),
);
diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart
index c7ef62bb..c6e17660 100644
--- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart
+++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart
@@ -192,9 +192,16 @@ class _WaitingAppointmentPaymentPageState extends State {
Spacer(),
LocaleKeys.balanceAmount.tr(context: context).toText14(isBold: true, color: Colors.white),
SizedBox(height: 4.h),
- Consumer(builder: (context, habibWalletVM, child) {
- return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount,
- textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false, letterSpacing: -1)
- .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h);
- }),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Consumer(builder: (context, habibWalletVM, child) {
+ return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount,
+ textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false, letterSpacing: -1)
+ .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h);
+ }),
+ Transform.flip(
+ flipX: appState.isArabic(),
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.arrow_forward,
+ height: 24.h,
+ width: 24.h,
+ iconColor: AppColors.whiteColor,
+ ),
+ ),
+ ],
+ ),
],
),
),
- ),
+ ).onPress(() {
+ showCommonBottomSheetWithoutHeight(
+ title: LocaleKeys.habibWallet.tr(context: context),
+ context,
+ // child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
+ child: HabibWalletBalanceList(),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ }),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart
index ded7aaeb..0e7a1eac 100644
--- a/lib/presentation/habib_wallet/recharge_wallet_page.dart
+++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart
@@ -149,7 +149,8 @@ class _RechargeWalletPageState extends State {
),
),
const Spacer(),
- LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, isBold: true),
+ ((getIt.get().getAuthenticatedUser()!.outSa == 1) ? LocaleKeys.aed.tr(context: context) : LocaleKeys.sar.tr(context: context))
+ .toText20(color: AppColors.greyTextColor, isBold: true),
],
),
],
diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart
index f07cbf63..f894c387 100644
--- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart
+++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart
@@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
+import 'package:hmg_patient_app_new/core/utils/date_util.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/string_extensions.dart';
@@ -178,9 +179,16 @@ class _WalletPaymentConfirmPageState extends State {
serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 1);
payfortViewModel.initiatePaymentWithCard(
+ "mada",
+ "0",
+ "false",
+ DateUtil.convertDateToString(DateTime.now()),
+ "0",
+ "0",
transactionID: transID,
fileNumber: habibWalletVM.fileNumber.toString(),
amount: habibWalletVM.walletRechargeAmount.toString(),
+ orderDescription: "Advance Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader();
@@ -204,7 +212,7 @@ class _WalletPaymentConfirmPageState extends State {
habibWalletVM.setIsWalletAmountToBeLoaded(true);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
- Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
+ // Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
@@ -353,9 +361,16 @@ class _WalletPaymentConfirmPageState extends State {
serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 1);
payfortViewModel.initiatePaymentWithCard(
+ "visa",
+ "0",
+ "false",
+ DateUtil.convertDateToString(DateTime.now()),
+ "0",
+ "0",
transactionID: transID,
fileNumber: habibWalletVM.fileNumber.toString(),
amount: habibWalletVM.walletRechargeAmount.toString(),
+ orderDescription: "Advance Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader();
@@ -380,7 +395,7 @@ class _WalletPaymentConfirmPageState extends State {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
- Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
+ // Navigator.of(getIt.get().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
onError: (err) {
diff --git a/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart b/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart
new file mode 100644
index 00000000..62817e98
--- /dev/null
+++ b/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.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/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:provider/provider.dart';
+
+class HabibWalletBalanceList extends StatelessWidget {
+ const HabibWalletBalanceList({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.r,
+ hasShadow: false,
+ ),
+ child: Consumer(builder: (context, habibWalletVM, child) {
+ return ListView.separated(
+ itemCount: habibWalletVM.habibWalletBalanceList.length,
+ physics: NeverScrollableScrollPhysics(),
+ padding: EdgeInsets.only(top: 8, bottom: 8),
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(child: habibWalletVM.habibWalletBalanceList[index].projectDescription!.toText16(isBold: true, color: AppColors.textColor)),
+ Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletBalanceList[index].patientAdvanceBalanceAmount!, //did the changes because of negative value
+ textColor: AppColors.textColor,
+ iconColor: AppColors.textColor,
+ iconSize: 18.h,
+ isExpanded: false,
+ fontSize: 28.f,
+ fontWeight: FontWeight.w600,
+ letterSpacing: -1),
+ ],
+ ).paddingSymmetrical(0, 12.h);
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: AppColors.textColor.withAlpha(20));
+ },
+ ).paddingSymmetrical(16.h, 24.h).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading);
+ }),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart
index 6cc1bb93..75144cf7 100644
--- a/lib/presentation/hmg_services/services_page.dart
+++ b/lib/presentation/hmg_services/services_page.dart
@@ -544,7 +544,7 @@ class _ServicesPageState extends State {
return Row(
children: [
Utils.buildSvgWithAssets(
- icon: AppAssets.saudi_riyal_icon,
+ icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon,
iconColor: AppColors.inputLabelTextColor,
width: 24.h,
height: 24.h,
@@ -554,7 +554,7 @@ class _ServicesPageState extends State {
NumberFormat.decimalPattern()
.format(habibWalletVM.habibWalletAmount)
.toString()
- .toText28(isBold: true, isEnglishOnly: true)
+ .toText28(isBold: true)
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
],
);
diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart
index 5f46f94f..85f1e972 100644
--- a/lib/presentation/home/landing_page.dart
+++ b/lib/presentation/home/landing_page.dart
@@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
@@ -144,6 +145,8 @@ class _LandingPageState extends State {
}
if (appState.isAuthenticated) {
+ Clarity.setCustomUserId(appState.getAuthenticatedUser()!.patientId.toString());
+
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
@@ -445,31 +448,31 @@ class _LandingPageState extends State {
: SizedBox.shrink(),
// Offers And Discounts Carousel - Auto-scrolling from right to left
- appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel
- ? Column(
- children: [
- SizedBox(height: 12.h),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true),
- Row(
- children: [
- LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true),
- SizedBox(width: 2.h),
- Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
- ],
- ),
- ],
- ).paddingSymmetrical(24.h, 0.h).onPress(() {
- Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage()));
- }),
- SizedBox(height: 16.h),
- OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h),
- SizedBox(height: 18.h),
- ],
- )
- : SizedBox.shrink(),
+ // appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel
+ // ? Column(
+ // children: [
+ // SizedBox(height: 12.h),
+ // Row(
+ // mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ // children: [
+ // ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true),
+ // Row(
+ // children: [
+ // LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true),
+ // SizedBox(width: 2.h),
+ // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
+ // ],
+ // ),
+ // ],
+ // ).paddingSymmetrical(24.h, 0.h).onPress(() {
+ // Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage()));
+ // }),
+ // SizedBox(height: 16.h),
+ // OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h),
+ // SizedBox(height: 18.h),
+ // ],
+ // )
+ // : SizedBox.shrink(),
appState.isAuthenticated
? Column(
@@ -513,7 +516,7 @@ class _LandingPageState extends State {
),
).paddingSymmetrical(24.h, 16.h)
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
- ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1
+ ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 && !immediateLiveCareVM.patientHasPendingLiveCareRequest
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
@@ -533,9 +536,11 @@ class _LandingPageState extends State {
height: isTablet ? 290.h : 255.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
- itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length < 3
- ? myAppointmentsVM.patientAppointmentsHistoryList.length
- : 3,
+ itemCount: () {
+ final int baseCount =
+ myAppointmentsVM.patientAppointmentsHistoryList.length + (immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0);
+ return baseCount < 3 ? baseCount : 3;
+ }(),
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
@@ -568,10 +573,14 @@ class _LandingPageState extends State {
height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space
child: Builder(
builder: (context) {
+ final int baseAppointmentCount = myAppointmentsVM.patientAppointmentsHistoryList.length;
+ final int liveCareCount = immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0;
+ final int totalCount = baseAppointmentCount + liveCareCount;
+
final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading
? 3
- : myAppointmentsVM.patientAppointmentsHistoryList.length < 3
- ? myAppointmentsVM.patientAppointmentsHistoryList.length
+ : totalCount < 3
+ ? totalCount
: 3;
final bool isArabic = appState.isArabic();
return Swiper(
@@ -1110,7 +1119,7 @@ class _LandingPageState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- _buildQueueHeader(currentStatus),
+ _buildQueueHeader(currentQueue, currentStatus),
SizedBox(height: 8.h),
_buildQueueGreeting(),
SizedBox(height: 2.h),
@@ -1119,11 +1128,11 @@ class _LandingPageState extends State {
color: AppColors.textColorLight,
),
SizedBox(height: 8.h),
- (currentQueue.queueNo ?? "IM-123").toText28(isBold: true).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty),
+ (currentQueue.queueNo ?? "IM-123").toText28(isBold: true).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
SizedBox(height: 6.h),
_buildServingNowSection(),
SizedBox(height: 5.h),
- _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty),
+ _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
],
),
),
@@ -1135,7 +1144,7 @@ class _LandingPageState extends State {
}
// Queue Card: Header with status chip and icon
- Widget _buildQueueHeader(int currentStatus) {
+ Widget _buildQueueHeader(PatientInQueueList currentQueue, int currentStatus) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -1144,7 +1153,7 @@ class _LandingPageState extends State {
labelText: currentStatus == 0 ? LocaleKeys.inQueue.tr() : LocaleKeys.yourTurn.tr(),
backgroundColor: Utils.getCardBorderColor(currentStatus).withValues(alpha: 0.20),
textColor: Utils.getCardBorderColor(currentStatus),
- ).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty),
+ ).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.greyColor,
diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart
index 1d9bd656..0bfba696 100644
--- a/lib/presentation/home/widgets/habib_wallet_card.dart
+++ b/lib/presentation/home/widgets/habib_wallet_card.dart
@@ -91,7 +91,7 @@ class HabibWalletCard extends StatelessWidget {
Row(
children: [
Utils.buildSvgWithAssets(
- icon: AppAssets.saudi_riyal_icon,
+ icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon,
iconColor: AppColors.inputLabelTextColor,
width: 24.h,
height: 24.h,
diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart
index 81343e42..66135dda 100644
--- a/lib/presentation/medical_file/medical_file_page.dart
+++ b/lib/presentation/medical_file/medical_file_page.dart
@@ -1226,10 +1226,13 @@ class _MedicalFilePageState extends State {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}".toText16(isBold: true),
+ (!appState.isArabic()
+ ? "${LocaleKeys.sick2.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}"
+ : "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}")
+ .toText16(isBold: true),
Row(
children: [
- LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
+ LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart
index c31d80ac..431a8faf 100644
--- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart
+++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart
@@ -53,7 +53,9 @@ class _PatientSickleavesListPageState extends State {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
- title: "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}",
+ title: (!appState.isArabic()
+ ? "${LocaleKeys.sick2.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}"
+ : "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}"),
child: SingleChildScrollView(
child: Consumer(builder: (context, model, child) {
return Column(
diff --git a/lib/presentation/medical_file/widgets/medical_report_card.dart b/lib/presentation/medical_file/widgets/medical_report_card.dart
index e2464392..1dc080c7 100644
--- a/lib/presentation/medical_file/widgets/medical_report_card.dart
+++ b/lib/presentation/medical_file/widgets/medical_report_card.dart
@@ -62,7 +62,7 @@ class MedicalReportCard extends StatelessWidget {
),
if (listReady.isNotEmpty)
AppCustomChipWidget(
- labelText: '${listRequest.length.toString().padLeft(2, '0')} ${LocaleKeys.readyToDownload.tr()}',
+ labelText: '${listReady.length.toString().padLeft(2, '0')} ${LocaleKeys.readyToDownload.tr()}',
icon: AppAssets.sucess,
iconColor: AppColors.successColor,
textColor: AppColors.successColor,
diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart
index 2f48bcbe..8feb8434 100644
--- a/lib/presentation/my_family/widget/family_cards.dart
+++ b/lib/presentation/my_family/widget/family_cards.dart
@@ -444,7 +444,7 @@ class _FamilyCardsState extends State {
// Original logic: final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID);
final canSwitch = true; // Disabled - allows all users to switch
return Container(
- padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h),
+ padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 12.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Opacity(
opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active
@@ -454,8 +454,8 @@ class _FamilyCardsState extends State {
mainAxisSize: MainAxisSize.min,
children: [
UserAvatarWidget(
- width: 72.h,
- height: 70.h,
+ width: 70.h,
+ height: 66.h,
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
@@ -463,7 +463,7 @@ class _FamilyCardsState extends State {
isCircular: true,
isFamilyMember: true, // Prevent AppState fallback
),
- SizedBox(height: 8.h),
+ SizedBox(height: 4.h),
(profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1),
// SizedBox(height: 8.h),
// CustomChipWidget(
@@ -476,14 +476,17 @@ class _FamilyCardsState extends State {
// textColor: AppColors.textColor),
widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(),
widget.isShowDetails
- ? CustomChipWidget(
- chipType: ChipTypeEnum.alert,
- backgroundColor: AppColors.lightGrayBGColor,
- chipText: "${LocaleKeys.age.tr(context: context)}: ${profile.age ?? "N/A"} ${LocaleKeys.years.tr(context: context)}",
- isShowBorder: false,
- borderRadius: 8.h,
- textColor: AppColors.textColor,
- )
+ ? Wrap(direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [
+ AppCustomChipWidget(
+ icon: AppAssets.file_icon,
+ richText: "${LocaleKeys.fileno.tr(context: context)}: ${profile.responseId}".toText10(isBold: true),
+ labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.age.tr(context: context)}: ${profile.age ?? "N/A"} ${LocaleKeys.years.tr(context: context)}",
+ labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 6.w),
+ ),
+ ])
: SizedBox(),
widget.isShowDetails
? SizedBox(height: 8.h)
diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart
index a5823b4f..968c4b5b 100644
--- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart
+++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart
@@ -104,6 +104,8 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget {
url: "https://maps.googleapis.com/maps/api/staticmap?center=${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&zoom=15&size=350x165&maptype=roadmap&markers=color:red%7C${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng",
iconColor: AppColors.blackBgColor,
fit: BoxFit.contain,
+ width: double.infinity,
+ height: 165.h,
),
),
],
diff --git a/lib/presentation/profile_settings/accessibility_page.dart b/lib/presentation/profile_settings/accessibility_page.dart
index 7fe9b61b..328c1936 100644
--- a/lib/presentation/profile_settings/accessibility_page.dart
+++ b/lib/presentation/profile_settings/accessibility_page.dart
@@ -417,17 +417,17 @@ class _AccessibilityPageState extends State {
// Price with SAR symbol
Row(
children: [
- "1234".toText24(
- isBold: true,
- color: AppColors.blackBgColor, // Always use base color
- ),
- SizedBox(width: 4.w),
SvgPicture.asset(
- AppAssets.saudi_riyal_icon,
+ (Utils.appState.getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon,
colorFilter: ColorFilter.mode(AppColors.blackBgColor, BlendMode.srcIn),
width: 16.w,
height: 16.h,
),
+ SizedBox(width: 4.h),
+ "1234".toText24(
+ isBold: true,
+ color: AppColors.blackBgColor, // Always use base color
+ ),
],
),
SizedBox(height: 16.h),
diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart
index 99ed0928..ae0a4c02 100644
--- a/lib/presentation/todo_section/ancillary_order_payment_page.dart
+++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart
@@ -171,9 +171,16 @@ class _AncillaryOrderPaymentPageState extends State {
);
payfortViewModel.initiatePaymentWithCard(
+ "mada",
+ "0",
+ "false",
+ DateUtil.convertDateToString(DateTime.now()),
+ "0",
+ "0",
transactionID: transID,
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
amount: widget.totalAmount.toString(),
+ orderDescription: "Ancillary Order Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
@@ -286,9 +293,16 @@ class _AncillaryOrderPaymentPageState extends State {
);
payfortViewModel.initiatePaymentWithCard(
+ "visa",
+ "0",
+ "false",
+ DateUtil.convertDateToString(DateTime.now()),
+ "0",
+ "0",
transactionID: transID,
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
amount: widget.totalAmount.toString(),
+ orderDescription: "Ancillary Order Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
diff --git a/lib/services/clarity_service.dart b/lib/services/clarity_service.dart
new file mode 100644
index 00000000..e8600821
--- /dev/null
+++ b/lib/services/clarity_service.dart
@@ -0,0 +1,291 @@
+import 'package:clarity_flutter/clarity_flutter.dart';
+import 'package:flutter/foundation.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+
+/// Centralized service for Clarity tracking and analytics
+/// Provides methods for tracking user actions, errors, and app state
+class ClarityService {
+ final AppState _appState;
+
+ ClarityService({required AppState appState}) : _appState = appState;
+
+ /// Initialize Clarity with user context
+ void setUserContext() {
+ if (kDebugMode) return; // Skip in debug mode
+
+ try {
+ if (_appState.isAuthenticated) {
+ final user = _appState.getAuthenticatedUser();
+ if (user != null) {
+ // Set user ID
+ Clarity.setCustomUserId(user.patientId.toString());
+
+ // Set user segments for filtering
+ Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown');
+ Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa');
+ Clarity.setCustomTag('auth_status', 'authenticated');
+ }
+ } else {
+ Clarity.setCustomTag('auth_status', 'guest');
+ }
+ } catch (e) {
+ debugPrint('Failed to set Clarity user context: $e');
+ }
+ }
+
+ /// Track screen views / route changes
+ void trackScreenView(String screenName, {Map? properties}) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('current_screen', _truncate(screenName, 100));
+
+ if (properties != null) {
+ properties.forEach((key, value) {
+ Clarity.setCustomTag(key, _truncate(value, 250));
+ });
+ }
+
+ Clarity.sendCustomEvent('screen_view_$screenName');
+ } catch (e) {
+ debugPrint('Failed to track screen view: $e');
+ }
+ }
+
+ /// Track user actions (button clicks, form submissions, etc.)
+ void trackAction(String actionName, {Map? properties}) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('action_name', _truncate(actionName, 100));
+
+ if (properties != null) {
+ properties.forEach((key, value) {
+ Clarity.setCustomTag('action_$key', _truncate(value, 250));
+ });
+ }
+
+ Clarity.sendCustomEvent('user_action_$actionName');
+ } catch (e) {
+ debugPrint('Failed to track action: $e');
+ }
+ }
+
+ /// Track API calls with success/failure
+ void trackApiCall({
+ required String endpoint,
+ required String method,
+ required bool isSuccess,
+ int? statusCode,
+ String? errorType,
+ String? errorMessage,
+ }) {
+ if (kDebugMode) return;
+
+ try {
+ final eventType = isSuccess ? 'api_success' : 'api_failure';
+
+ Clarity.setCustomTag('api_endpoint', _getEndpointName(endpoint));
+ Clarity.setCustomTag('api_method', method);
+ Clarity.setCustomTag('api_status', isSuccess ? 'success' : 'failure');
+
+ if (statusCode != null) {
+ Clarity.setCustomTag('api_status_code', statusCode.toString());
+ }
+
+ if (errorType != null) {
+ Clarity.setCustomTag('api_error_type', _truncate(errorType, 100));
+ }
+
+ if (errorMessage != null) {
+ Clarity.setCustomTag('api_error_msg', _truncate(errorMessage, 250));
+ }
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('api_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent(eventType);
+ } catch (e) {
+ debugPrint('Failed to track API call: $e');
+ }
+ }
+
+ /// Track app errors and exceptions
+ void trackError({
+ required String errorType,
+ required String errorMessage,
+ String? stackTrace,
+ bool isFatal = false,
+ Map? additionalContext,
+ }) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('error_type', _truncate(errorType, 100));
+ Clarity.setCustomTag('error_fatal', isFatal.toString());
+ Clarity.setCustomTag('error_message', _truncate(errorMessage, 250));
+
+ if (stackTrace != null) {
+ // Only send first 250 chars of stack trace
+ Clarity.setCustomTag('error_stack', _truncate(stackTrace, 250));
+ }
+
+ if (additionalContext != null) {
+ additionalContext.forEach((key, value) {
+ Clarity.setCustomTag('error_$key', _truncate(value, 250));
+ });
+ }
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('error_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent(isFatal ? 'app_fatal_error' : 'app_error');
+ } catch (e) {
+ debugPrint('Failed to track error: $e');
+ }
+ }
+
+ /// Track user frustration events (rage clicks, dead clicks)
+ void trackFrustration({
+ required String frustrationType,
+ required String elementLabel,
+ Map? context,
+ }) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('frustration_type', frustrationType);
+ Clarity.setCustomTag('frustration_element', _truncate(elementLabel, 100));
+
+ if (context != null) {
+ context.forEach((key, value) {
+ Clarity.setCustomTag('frustration_$key', _truncate(value, 250));
+ });
+ }
+
+ Clarity.sendCustomEvent('user_frustration_$frustrationType');
+ } catch (e) {
+ debugPrint('Failed to track frustration: $e');
+ }
+ }
+
+ /// Track feature usage
+ void trackFeatureUsage(String featureName, {Map? metadata}) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('feature_name', _truncate(featureName, 100));
+
+ if (metadata != null) {
+ metadata.forEach((key, value) {
+ Clarity.setCustomTag('feature_$key', _truncate(value, 250));
+ });
+ }
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('feature_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('feature_used_$featureName');
+ } catch (e) {
+ debugPrint('Failed to track feature usage: $e');
+ }
+ }
+
+ /// Track payment events
+ void trackPayment({
+ required String paymentStatus,
+ String? paymentMethod,
+ String? amount,
+ String? transactionId,
+ }) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('payment_status', paymentStatus);
+
+ if (paymentMethod != null) {
+ Clarity.setCustomTag('payment_method', paymentMethod);
+ }
+
+ if (amount != null) {
+ Clarity.setCustomTag('payment_amount', amount);
+ }
+
+ if (transactionId != null) {
+ Clarity.setCustomTag('payment_tx_id', _truncate(transactionId, 100));
+ }
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('payment_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('payment_$paymentStatus');
+ } catch (e) {
+ debugPrint('Failed to track payment: $e');
+ }
+ }
+
+ /// Track appointment booking flow
+ void trackAppointmentEvent({
+ required String eventType,
+ String? doctorId,
+ String? clinicId,
+ String? appointmentDate,
+ }) {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('appointment_event', eventType);
+
+ if (doctorId != null) {
+ Clarity.setCustomTag('appointment_doctor', doctorId);
+ }
+
+ if (clinicId != null) {
+ Clarity.setCustomTag('appointment_clinic', clinicId);
+ }
+
+ if (appointmentDate != null) {
+ Clarity.setCustomTag('appointment_date', appointmentDate);
+ }
+
+ if (_appState.isAuthenticated) {
+ Clarity.setCustomTag('appointment_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
+ }
+
+ Clarity.sendCustomEvent('appointment_$eventType');
+ } catch (e) {
+ debugPrint('Failed to track appointment event: $e');
+ }
+ }
+
+ /// Clear user context on logout
+ void clearUserContext() {
+ if (kDebugMode) return;
+
+ try {
+ Clarity.setCustomTag('auth_status', 'guest');
+ Clarity.setCustomTag('user_action', 'logout');
+ Clarity.sendCustomEvent('user_logged_out');
+ } catch (e) {
+ debugPrint('Failed to clear Clarity user context: $e');
+ }
+ }
+
+ /// Helper: Truncate string to fit Clarity's limits
+ String _truncate(String text, int maxLength) {
+ if (text.length <= maxLength) return text;
+ return '...${text.substring(text.length - (maxLength - 3))}';
+ }
+
+ /// Helper: Extract endpoint name from full URL
+ String _getEndpointName(String endpoint) {
+ final cleanEndpoint = endpoint.split('/').last;
+ return _truncate(cleanEndpoint, 100);
+ }
+}
+
+
diff --git a/lib/splashPage.dart b/lib/splashPage.dart
index 2edf222e..dc49428a 100644
--- a/lib/splashPage.dart
+++ b/lib/splashPage.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:io';
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:flutter/foundation.dart';
@@ -10,6 +11,7 @@ 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/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.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/extensions/widget_extensions.dart';
@@ -91,6 +93,45 @@ class _SplashScreenState extends State {
);
Clarity.initialize(context, config);
+
+ // Set initial app context tags
+ try {
+ final appState = getIt.get();
+
+ // Set device and app metadata
+ Clarity.setCustomTag('app_version', ApiConsts.appVersionID.toString());
+ Clarity.setCustomTag('device_type', appState.deviceTypeID ?? 'unknown');
+ Clarity.setCustomTag('platform', Platform.isIOS ? 'ios' : 'android');
+
+ // Set user context if authenticated
+ if (appState.isAuthenticated) {
+ final user = appState.getAuthenticatedUser();
+ if (user != null) {
+ // Set user ID for session tracking
+ Clarity.setCustomUserId(user.patientId.toString());
+
+ // Set custom tags for filtering sessions
+ Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown');
+ Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa');
+ Clarity.setCustomTag('auth_status', 'authenticated');
+
+ // Optional: Set other non-sensitive user properties
+ if (user.mobileNumber != null) {
+ // Mask phone number for privacy (show only last 4 digits)
+ final maskedPhone = '***${user.mobileNumber!.substring(user.mobileNumber!.length - 4)}';
+ Clarity.setCustomTag('user_phone_masked', maskedPhone);
+ }
+ }
+ } else {
+ Clarity.setCustomTag('auth_status', 'guest');
+ }
+
+ // Send initialization event
+ Clarity.sendCustomEvent('app_initialized');
+
+ } catch (e) {
+ debugPrint('Failed to set Clarity user context: $e');
+ }
}
navigateToTeleConsult() async {
diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart
index ff8f9c59..c2ae0272 100644
--- a/lib/widgets/app_language_change.dart
+++ b/lib/widgets/app_language_change.dart
@@ -75,6 +75,7 @@ class _AppLanguageChangeState extends State {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
+ habibWalletVM.setIsWalletAmountToBeLoaded(true);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart
index 3f4902e0..f3eb94fb 100644
--- a/lib/widgets/buttons/custom_button.dart
+++ b/lib/widgets/buttons/custom_button.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@@ -60,39 +61,73 @@ class CustomButton extends StatelessWidget {
final fontS = fontSize ?? (16.f);
final isButtonDisabled = isDisabled || onPressed == null;
- return GestureDetector(
- onTap: isButtonDisabled ? null : onPressed,
- child: Container(
- height: height == null
- ? 56.h
- : (isFoldable || isTablet)
- ? height! + 10.h
- : height,
- width: width,
- padding: padding,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor,
- borderRadius: radius,
- customBorder: BorderRadius.circular(radius),
- side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (icon != null)
- Padding(
- padding: text.isNotEmpty ? EdgeInsets.only(right: 6.w, left: 6.w) : EdgeInsets.zero,
- child: Utils.buildSvgWithAssets(
- icon: icon!,
- iconColor: isButtonDisabled ? AppColors.greyTextColor : iconColor,
- isDisabled: isButtonDisabled,
- width: iconS,
- height: iconS,
- applyThemeColor: applyThemeColor),
- ),
- if (text.isNotEmpty && icon != null)
- Flexible(
- child: Text(
+ return Semantics(
+ button: true,
+ enabled: !isButtonDisabled,
+ label: text,
+ hint: isButtonDisabled ? 'Button is disabled' : 'Tap to $text',
+ child: GestureDetector(
+ onTap: isButtonDisabled
+ ? null
+ : () {
+ // Log button tap to Clarity before executing callback
+ try {
+ Clarity.setCustomTag('button_text', text.length > 100 ? text.substring(0, 100) : text);
+ Clarity.setCustomTag('button_action', 'tap');
+ Clarity.setCustomTag('button_disabled', 'false');
+ Clarity.sendCustomEvent('button_tapped');
+ } catch (e) {
+ // Silence Clarity errors to not interrupt user flow
+ }
+
+ // Execute the actual button callback
+ onPressed?.call();
+ },
+ child: Container(
+ height: height == null
+ ? 56.h
+ : (isFoldable || isTablet)
+ ? height! + 10.h
+ : height,
+ width: width,
+ padding: padding,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor,
+ borderRadius: radius,
+ customBorder: BorderRadius.circular(radius),
+ side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (icon != null)
+ Padding(
+ padding: text.isNotEmpty ? EdgeInsets.only(right: 6.w, left: 6.w) : EdgeInsets.zero,
+ child: Utils.buildSvgWithAssets(
+ icon: icon!,
+ iconColor: isButtonDisabled ? AppColors.greyTextColor : iconColor,
+ isDisabled: isButtonDisabled,
+ width: iconS,
+ height: iconS,
+ applyThemeColor: applyThemeColor),
+ ),
+ if (text.isNotEmpty && icon != null)
+ Flexible(
+ child: Text(
+ text,
+ overflow: textOverflow ?? TextOverflow.ellipsis,
+ maxLines: 1,
+ textAlign: TextAlign.center,
+ style: context.dynamicTextStyle(
+ fontSize: fontS,
+ color: isButtonDisabled ? AppColors.greyTextColor : textColor,
+ letterSpacing: 0,
+ fontWeight: isBold ? FontWeight.w700 : fontWeight,
+ ),
+ ),
+ ),
+ if (text.isNotEmpty && icon == null)
+ Text(
text,
overflow: textOverflow ?? TextOverflow.ellipsis,
maxLines: 1,
@@ -104,22 +139,9 @@ class CustomButton extends StatelessWidget {
fontWeight: isBold ? FontWeight.w700 : fontWeight,
),
),
- ),
- if (text.isNotEmpty && icon == null)
- Text(
- text,
- overflow: textOverflow ?? TextOverflow.ellipsis,
- maxLines: 1,
- textAlign: TextAlign.center,
- style: context.dynamicTextStyle(
- fontSize: fontS,
- color: isButtonDisabled ? AppColors.greyTextColor : textColor,
- letterSpacing: 0,
- fontWeight: isBold ? FontWeight.w700 : fontWeight,
- ),
- ),
- ],
- ),
- ));
+ ],
+ ),
+ )),
+ );
}
}
diff --git a/lib/widgets/clarity_interaction_tracker.dart b/lib/widgets/clarity_interaction_tracker.dart
new file mode 100644
index 00000000..e27aec86
--- /dev/null
+++ b/lib/widgets/clarity_interaction_tracker.dart
@@ -0,0 +1,200 @@
+import 'package:flutter/material.dart';
+import 'package:clarity_flutter/clarity_flutter.dart';
+
+/// A wrapper widget that tracks user interactions with Clarity for UX frustration analysis.
+/// Helps identify rage clicks, dead clicks, and rapid scrolls.
+///
+/// Usage:
+/// ```dart
+/// ClarityInteractionTracker(
+/// semanticLabel: 'Login Button',
+/// interactionType: ClarityInteractionType.button,
+/// onTap: () { /* your action */ },
+/// child: YourWidget(),
+/// )
+/// ```
+class ClarityInteractionTracker extends StatelessWidget {
+ final Widget child;
+ final String semanticLabel;
+ final VoidCallback? onTap;
+ final ClarityInteractionType interactionType;
+ final Map? customTags;
+ final bool enabled;
+
+ const ClarityInteractionTracker({
+ super.key,
+ required this.child,
+ required this.semanticLabel,
+ this.onTap,
+ this.interactionType = ClarityInteractionType.button,
+ this.customTags,
+ this.enabled = true,
+ });
+
+ void _logInteraction(BuildContext context) {
+ if (!enabled) return;
+
+ try {
+ // Set common tags
+ Clarity.setCustomTag('interaction_type', interactionType.name);
+ Clarity.setCustomTag('interaction_label',
+ semanticLabel.length > 100 ? semanticLabel.substring(0, 100) : semanticLabel
+ );
+ Clarity.setCustomTag('interaction_enabled', enabled.toString());
+
+ // Set route/screen name if available
+ final route = ModalRoute.of(context);
+ if (route != null && route.settings.name != null) {
+ Clarity.setCustomTag('screen_route', route.settings.name!);
+ }
+
+ // Add custom tags if provided
+ if (customTags != null) {
+ customTags!.forEach((key, value) {
+ // Ensure key and value fit Clarity's 255 char limit
+ final truncatedValue = value.length > 250 ? value.substring(0, 250) : value;
+ Clarity.setCustomTag(key, truncatedValue);
+ });
+ }
+
+ // Send event to Clarity
+ Clarity.sendCustomEvent('user_interaction_${interactionType.name}');
+ } catch (e) {
+ // Silence Clarity errors to not interrupt user flow
+ debugPrint('Clarity interaction tracking error: $e');
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Semantics(
+ label: semanticLabel,
+ button: interactionType == ClarityInteractionType.button,
+ enabled: enabled,
+ excludeSemantics: false,
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: enabled && onTap != null
+ ? () {
+ _logInteraction(context);
+ onTap?.call();
+ }
+ : null,
+ child: child,
+ ),
+ );
+ }
+}
+
+/// Types of interactions that can be tracked
+enum ClarityInteractionType {
+ button,
+ link,
+ card,
+ listItem,
+ icon,
+ image,
+ input,
+ custom,
+}
+
+/// Extension to easily wrap any widget with Clarity tracking
+extension ClarityTrackingExtension on Widget {
+ /// Wraps this widget with Clarity interaction tracking
+ Widget trackWithClarity({
+ required String label,
+ VoidCallback? onTap,
+ ClarityInteractionType type = ClarityInteractionType.custom,
+ Map? customTags,
+ bool enabled = true,
+ }) {
+ return ClarityInteractionTracker(
+ semanticLabel: label,
+ onTap: onTap,
+ interactionType: type,
+ customTags: customTags,
+ enabled: enabled,
+ child: this,
+ );
+ }
+}
+
+/// Wrapper for ScrollView to track rapid scrolling behavior
+class ClarityScrollTracker extends StatefulWidget {
+ final Widget child;
+ final String scrollViewLabel;
+ final ScrollController? controller;
+
+ const ClarityScrollTracker({
+ super.key,
+ required this.child,
+ required this.scrollViewLabel,
+ this.controller,
+ });
+
+ @override
+ State createState() => _ClarityScrollTrackerState();
+}
+
+class _ClarityScrollTrackerState extends State {
+ late ScrollController _controller;
+ bool _isRapidScrolling = false;
+ DateTime? _lastScrollTime;
+ int _rapidScrollCount = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = widget.controller ?? ScrollController();
+ _controller.addListener(_onScroll);
+ }
+
+ void _onScroll() {
+ final now = DateTime.now();
+
+ // Detect rapid scrolling (scroll events within 100ms)
+ if (_lastScrollTime != null) {
+ final timeDiff = now.difference(_lastScrollTime!).inMilliseconds;
+
+ if (timeDiff < 100) {
+ _rapidScrollCount++;
+
+ if (!_isRapidScrolling && _rapidScrollCount > 5) {
+ _isRapidScrolling = true;
+ _logRapidScroll();
+ }
+ } else if (timeDiff > 500) {
+ // Reset if scrolling slows down
+ _isRapidScrolling = false;
+ _rapidScrollCount = 0;
+ }
+ }
+
+ _lastScrollTime = now;
+ }
+
+ void _logRapidScroll() {
+ try {
+ Clarity.setCustomTag('scroll_type', 'rapid');
+ Clarity.setCustomTag('scroll_view', widget.scrollViewLabel);
+ Clarity.setCustomTag('scroll_position', _controller.position.pixels.toString());
+ Clarity.sendCustomEvent('rapid_scroll_detected');
+ } catch (e) {
+ debugPrint('Clarity scroll tracking error: $e');
+ }
+ }
+
+ @override
+ void dispose() {
+ if (widget.controller == null) {
+ _controller.dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child;
+ }
+}
+
diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart
index 16a955c8..b7fe686f 100644
--- a/lib/widgets/input_widget.dart
+++ b/lib/widgets/input_widget.dart
@@ -1,5 +1,6 @@
import 'dart:ui' as ui;
+import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -405,64 +406,66 @@ class TextInputWidget extends StatelessWidget {
child: Localizations.override(
context: context,
locale: const Locale('en', 'US'), // Force English locale for TextField
- child: TextField(
- hintLocales: const [Locale('en', 'US')],
- enabled: isEnable,
- scrollPadding: EdgeInsets.zero,
- keyboardType: isMultiline
- ? TextInputType.multiline
- : (isWalletAmountInput! ? const TextInputType.numberWithOptions(decimal: true) : keyboardType),
- controller: controller,
- readOnly: isReadOnly,
- textAlignVertical: TextAlignVertical.top,
- textAlign: isArabic ? TextAlign.right : TextAlign.left,
- textDirection: ui.TextDirection.ltr,
- onChanged: onChange,
- focusNode: focusNode ?? _focusNode,
- autofocus: autoFocus,
- textInputAction: TextInputAction.done,
- cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
- maxLength: isWalletAmountInput! ? 7 : 100,
- inputFormatters: isWalletAmountInput!
- ? [
- _ThousandSeparatorInputFormatter(),
- ]
- : null,
- onTapOutside: (event) {
- FocusManager.instance.primaryFocus?.unfocus();
- },
- onSubmitted: onSubmitted,
- minLines: isMultiline ? minLines : 1,
- maxLines: isMultiline ? maxLines : 1,
- style: TextStyle(
- fontSize: fontS,
- height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
- fontWeight: FontWeight.w600,
- color: AppColors.textColor,
- letterSpacing: -1,
- fontFamily: fontFamily,
- locale: const Locale('en', 'US'), // Force English locale for text style
- ),
- decoration: InputDecoration(
- counterText: "",
- isDense: true,
- hintText: hintText,
- hintStyle: TextStyle(
- fontFamily: isArabic ? 'CairoArabic' : 'Poppins',
- fontSize: 14.f,
- height: 21 / 16,
+ child: ClarityUnmask(
+ child: TextField(
+ hintLocales: const [Locale('en', 'US')],
+ enabled: isEnable,
+ scrollPadding: EdgeInsets.zero,
+ keyboardType: isMultiline
+ ? TextInputType.multiline
+ : (isWalletAmountInput! ? const TextInputType.numberWithOptions(decimal: true) : keyboardType),
+ controller: controller,
+ readOnly: isReadOnly,
+ textAlignVertical: TextAlignVertical.top,
+ textAlign: isArabic ? TextAlign.right : TextAlign.left,
+ textDirection: ui.TextDirection.ltr,
+ onChanged: onChange,
+ focusNode: focusNode ?? _focusNode,
+ autofocus: autoFocus,
+ textInputAction: TextInputAction.done,
+ cursorHeight: isWalletAmountInput! ? 40.h : 20.h,
+ maxLength: isWalletAmountInput! ? 7 : 100,
+ inputFormatters: isWalletAmountInput!
+ ? [
+ _ThousandSeparatorInputFormatter(),
+ ]
+ : null,
+ onTapOutside: (event) {
+ FocusManager.instance.primaryFocus?.unfocus();
+ },
+ onSubmitted: onSubmitted,
+ minLines: isMultiline ? minLines : 1,
+ maxLines: isMultiline ? maxLines : 1,
+ style: TextStyle(
+ fontSize: fontS,
+ height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
fontWeight: FontWeight.w600,
- color: hintColor != null ? AppColors.textColor : Color(0xff898A8D),
- letterSpacing: -0.75,
+ color: AppColors.textColor,
+ letterSpacing: -1,
+ fontFamily: fontFamily,
+ locale: const Locale('en', 'US'), // Force English locale for text style
+ ),
+ decoration: InputDecoration(
+ counterText: "",
+ isDense: true,
+ hintText: hintText,
+ hintStyle: TextStyle(
+ fontFamily: isArabic ? 'CairoArabic' : 'Poppins',
+ fontSize: 14.f,
+ height: 21 / 16,
+ fontWeight: FontWeight.w600,
+ color: hintColor != null ? AppColors.textColor : Color(0xff898A8D),
+ letterSpacing: -0.75,
+ ),
+ prefixIconConstraints: BoxConstraints(minWidth: 30.h),
+ prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true),
+ contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0),
+ border: InputBorder.none,
+ focusedBorder: InputBorder.none,
+ enabledBorder: InputBorder.none,
+ // Show red border when field has error - but we handle this in the parent Container
+ // The parent Container in build() already shows red border based on hasError flag
),
- prefixIconConstraints: BoxConstraints(minWidth: 30.h),
- prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true),
- contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0),
- border: InputBorder.none,
- focusedBorder: InputBorder.none,
- enabledBorder: InputBorder.none,
- // Show red border when field has error - but we handle this in the parent Container
- // The parent Container in build() already shows red border based on hasError flag
),
),
),
diff --git a/pubspec.yaml b/pubspec.yaml
index 880ec18e..ba0c0ed5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -87,7 +87,7 @@ dependencies:
open_filex: ^4.7.0
flutter_swiper_view:
path: package/flutter_swiper_view
- flutter_callkit_incoming: ^3.0.0
+ flutter_callkit_incoming: 3.0.0
in_app_update: ^4.2.5
# device_preview: ^1.3.1
image_picker_android: ^0.8.13+16