diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index c70123d4..13f778ff 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -36,7 +36,7 @@ android {
applicationId = "com.cloudsolutions.HMGPatientApp"
// minSdk = 24
minSdk = 29
- targetSdk = 35
+ targetSdk = 36
compileSdk = 36
// targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..360ab55d
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,26 @@
+
+ #ffffff
+ #ED1C2B
+ #444647
+ #2E3039
+ #2E3039
+ #a5a5a5
+ #e1e1e1
+ #FFF
+ #1e1e1e
+ #ED1C2B
+ #F9FAFD
+ #F2FAFD
+ #EC2213
+ #4D6E6E6E
+ #FFFFFFFF
+ #00000000
+ #3db39e
+ #00000000
+ #c7c7c7
+ #4c4c4c
+ #4c4c4c
+ #a5a5a5
+ #ED1C2B
+ #FFF
+
\ No newline at end of file
diff --git a/assets/images/svg/no_image_found.svg b/assets/images/svg/no_image_found.svg
new file mode 100644
index 00000000..faea08cd
--- /dev/null
+++ b/assets/images/svg/no_image_found.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/assets/images/svg/patient_qr_code_icon.svg b/assets/images/svg/patient_qr_code_icon.svg
new file mode 100644
index 00000000..c76f0326
--- /dev/null
+++ b/assets/images/svg/patient_qr_code_icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json
index 9233f923..971677fa 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -123,7 +123,7 @@
"forgotFileNoTitle": "نسيت الملف الطبي",
"enterNationalId": "يرجى إدخال رقم الهاتف المحمول ورقم الهوية",
"profileInfo": "يرجى إدخال معلومات الملف الشخصي",
- "submit": "بحث",
+ "submit": "تأكيد",
"forgotDesc": "أدخل رقم الهاتف المحمول لاستلام رقم الملف الطبي عبر الرسائل القصيرة",
"dob": "تاريخ الميلاد",
"hijriDate": "التاريخ الهجري",
@@ -1390,7 +1390,6 @@
"medicalFiles": "الملفات الطبية",
"addANewFamilyMember": "اضافة",
"viewInvoiceDetails": "عرض تفاصيل الفاتورة",
- "outPatient": "مريض خارجي",
"invoiceDetails": "تفاصيل الفاتورة",
"sendingEmailPleaseWait": "جاري إرسال ال��ريد الإلكتروني، يرجى الانتظار...",
"emailSentSuccessfullyMessage": "تم إرسال البريد الإلكتروني بنجاح.",
@@ -1950,7 +1949,7 @@
"transactions": "المعاملات",
"pharmacy": "الصيدلية",
"visitsOrders": "الزيارات/الطلبات",
- "earned": "حصل",
+ "earned": "مكتسب",
"getParkingQR": "الحصول على رمز الاستجابة السريعة لوقوف السيارات",
"cannotOpenThisFile": "لا يمكن فتح هذا الملف",
"thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد",
@@ -1977,11 +1976,15 @@
"pharmacyRefundPhysicalVisitNotice": "تتطلب استردادات الصيدلية زيارة شخصية لتسليم الأدوية ومعالجة الاسترداد.",
"parkingQR": "رمز الاستجابة السريعة لوقوف السيارات",
"parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:",
- "livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.",
- "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟",
- "schedule": "جدول",
- "doctorSchedule": "جدول الطبيب",
- "weeklyShift": "الفترات الأسبوعية",
- "dayOff": "إجازة",
- "notAvailable": "غير متاح"
+ "livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.",
+ "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟",
+ "schedule": "جدول",
+ "doctorSchedule": "جدول الطبيب",
+ "weeklyShift": "الفترات الأسبوعية",
+ "dayOff": "إجازة",
+ "notAvailable": "غير متاح",
+ "hide": "إخفاء",
+ "viewPatientQR": "عرض رمز الاستجابة السريعة للمريض",
+ "patientQR": "رمز الاستجابة السريعة للمريض",
+ "forText": "عن"
}
diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json
index 3098d0cb..7bcac22d 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -21,7 +21,7 @@
"mySchedule": "My Schedule",
"logout": "Logout",
"respirationRate": "Respiration Rate",
- "bookAppo": "New Appointment",
+ "bookAppo": "Book Appointment",
"searchBy": "Search By:",
"clinic": "Clinic",
"byClinic": "By Clinic",
@@ -1967,13 +1967,17 @@
"pharmacyRefundPhysicalVisitNotice": "Pharmacy refunds require a physical visit to hand over medications and process the refund.",
"parkingQR": "Parking QR Code",
"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",
- "doctorSchedule": "Doctor Schedule",
- "weeklyShift": "Weekly Shift",
- "dayOff": "Off",
- "notAvailable": "Not Available"
+ "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",
+ "doctorSchedule": "Doctor Schedule",
+ "weeklyShift": "Weekly Shift",
+ "dayOff": "Off",
+ "notAvailable": "Not Available",
+ "hide": "Hide",
+ "viewPatientQR": "View Patient QR Code",
+ "patientQR": "Patient QR Code",
+ "forText": "For"
}
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index fdc3bdfa..f52d12fc 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -8,8 +8,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 1587365701FF0D2AAAFED0D8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+ 37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; };
61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; };
@@ -100,7 +100,7 @@
76B1D2512F91BF8000DBBD1B /* Penguin.xcframework in Frameworks */,
76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */,
76B1D2532F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Frameworks */,
- DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */,
+ 37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -339,10 +339,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -356,10 +360,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -519,7 +527,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
+ CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -529,7 +537,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.0.5;
+ MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -706,7 +714,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
+ CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -716,7 +724,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.0.5;
+ MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -736,7 +744,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
+ CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -746,7 +754,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.0.5;
+ MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 5b3b2681..caa84333 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -86,7 +86,7 @@
NSMicrophoneUsageDescription
This app requires microphone access to enable virtual consultation between patient & doctor
NSMotionUsageDescription
- This app requires motion detection access to function properly.
+ This app requires access to motion detection to count your daily steps.
NSPhotoLibraryUsageDescription
This app requires photo library access to select image as document & upload it.
NSPhotoLibraryAddUsageDescription
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index b6480f73..cbfa1b4a 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -3,7 +3,7 @@ import 'package:flutter_amazonpaymentservices/environment_type.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
- static const maxSmallScreen = 660;
+ static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
@@ -303,7 +303,7 @@ class ApiConsts {
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
- static final double appVersionID = 21.5;
+ static final double appVersionID = 21.8;
static final int appChannelId = 3;
static final String appIpAddress = "10.20.10.20";
@@ -919,8 +919,6 @@ var AUTO_GENERATE_INVOICE_TAMARA = 'Services/PayFort_Serv.svc/REST/Tamara_Getinf
var GET_ONESIGNAL_VOIP_TOKEN = 'https://onesignal.com/api/v1/players';
-var CANCEL_PHARMA_LIVECARE_REQUEST = 'https://vcallapi.hmg.com/api/PharmaLiveCare/SendPaymentStatus';
-
var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs';
var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality';
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 8db7ec8e..3b242153 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -400,6 +400,8 @@ class AppAssets {
static const String habibLogoOnly = '$svgBasePath/habib logo_only.svg';
static const String descriptionIcon = '$svgBasePath/description_icon.svg';
static const String selectImageIcon = '$svgBasePath/select_image_icon.svg';
+ static const String patientQRCodeIcon = '$svgBasePath/patient_qr_code_icon.svg';
+ static const String noImageFound = '$svgBasePath/no_image_found.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index c4f7402c..e4ca4c25 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -84,6 +84,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/services/permission_service.dart';
+import 'package:hmg_patient_app_new/core/services/turnstile_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:local_auth/local_auth.dart';
@@ -155,6 +156,7 @@ class AppDependencies {
);
getIt.registerLazySingleton(() => PermissionService());
+ getIt.registerLazySingleton(() => TurnstileService(getIt()));
// Repositories
getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt()));
@@ -255,7 +257,8 @@ class AppDependencies {
dialogService: getIt(),
appState: getIt(),
errorHandlerService: getIt(),
- localAuthService: getIt()),
+ localAuthService: getIt(),
+ turnstileService: getIt()),
);
getIt.registerLazySingleton(() => ProfileSettingsViewModel(
@@ -366,7 +369,11 @@ class AppDependencies {
errorHandlerService: getIt(),
));
- getIt.registerLazySingleton(() => PayTabsViewModel(appState: getIt()));
+ getIt.registerLazySingleton(() => PayTabsViewModel(
+ appState: getIt(),
+ payfortRepo: getIt(),
+ errorHandlerService: getIt(),
+ ));
getIt.registerLazySingleton(
() => ClarityService(appState: getIt.get()),
diff --git a/lib/core/services/turnstile_service.dart b/lib/core/services/turnstile_service.dart
new file mode 100644
index 00000000..b30113e0
--- /dev/null
+++ b/lib/core/services/turnstile_service.dart
@@ -0,0 +1,60 @@
+import 'dart:async';
+import 'package:cloudflare_turnstile/cloudflare_turnstile.dart';
+import 'package:flutter/foundation.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+/// Service to handle Cloudflare Turnstile captcha verification
+class TurnstileService {
+ final LoggerService _loggerService;
+
+ // Replace with your Cloudflare Turnstile site key
+ static const String _siteKey = '0x4AAAAAAAkmbvEy63pcxBBt';
+
+ TurnstileService(this._loggerService);
+
+ /// Get Turnstile token using invisible mode
+ /// Returns the token on success, null on failure
+ Future getTurnstileToken() async {
+ // Initialize an instance of invisible Cloudflare Turnstile with your site key
+ final turnstile = CloudflareTurnstile.invisible(
+ siteKey: _siteKey,
+ onTimeout: () {},
+ options: TurnstileOptions(
+ retryInterval: const Duration(milliseconds: 2000),
+ ),
+ );
+
+ try {
+ // Get the Turnstile token
+ final token = await turnstile.getToken();
+ _loggerService.logInfo('Turnstile token retrieved successfully');
+ return token; // Return the token upon success
+ } on TurnstileException catch (e) {
+ // Handle Turnstile failure
+ _loggerService.logError('Turnstile challenge failed: ${e.message}');
+ if (kDebugMode) {
+ print('Challenge failed: ${e.message}');
+ }
+ } catch (e) {
+ // Handle any other exceptions
+ _loggerService.logError('Unexpected error during Turnstile challenge: $e');
+ if (kDebugMode) {
+ print('Unexpected error: $e');
+ }
+ } finally {
+ // Ensure the Turnstile instance is properly disposed of
+ turnstile.dispose();
+ }
+
+ // Return null if the token couldn't be generated
+ return null;
+ }
+
+ /// Get the site key for Turnstile widget
+ String getSiteKey() => _siteKey;
+
+ /// Validate if Turnstile is properly configured
+ bool isConfigured() {
+ return _siteKey.isNotEmpty && _siteKey != '0x4AAAAAAAkmbvEy63pcxBBt';
+ }
+}
diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart
index 0994171f..97c8dad0 100644
--- a/lib/core/utils/date_util.dart
+++ b/lib/core/utils/date_util.dart
@@ -18,7 +18,8 @@ class DateUtil {
final endIndex = date.indexOf(end, startIndex + start.length);
return DateTime.fromMillisecondsSinceEpoch(int.parse(
date.substring(startIndex + start.length, endIndex),
- ), isUtc: true).add(Duration(hours: 3));
+ // ), isUtc: true).add(Duration(hours: 3));
+ ), isUtc: true).toLocal();
}
static DateTime convertStringToDateSaudiTimezone(String date, int projectId) {
diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart
index e179a1ab..89f6969a 100644
--- a/lib/core/utils/push_notification_handler.dart
+++ b/lib/core/utils/push_notification_handler.dart
@@ -19,7 +19,12 @@ 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/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart';
+import 'package:hmg_patient_app_new/presentation/notifications/notification_details_page.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
@@ -523,6 +528,26 @@ class PushNotificationHandler {
_incomingCall(remoteMessage.data);
// showCallkitIncoming();
} else {
+ NotificationResponseModel notification = NotificationResponseModel();
+
+ notification.createdOn = DateUtil.convertDateToString(DateTime.now());
+ notification.isSentOn = DateUtil.convertDateToString(DateTime.now());
+ notification.messageTypeData = remoteMessage.data['picture'];
+ notification.messageType = notification.messageTypeData != null && notification.messageTypeData!.isNotEmpty ? "image" : "";
+ notification.message = remoteMessage.data['message'];
+ notification.notificationType = remoteMessage.data["NotificationType"].toString();
+ if (remoteMessage.data["NotificationType"] == "2") {
+ notification.videoURL = remoteMessage.data["VideoUrl"];
+ }
+
+ Navigator.of(getIt.get().navigatorKey.currentContext!).push(
+ CustomPageRoute(
+ page: NotificationDetailsPage(
+ notification: notification,
+ ),
+ ),
+ );
+
// GetNotificationsResponseModel notification = new GetNotificationsResponseModel();
//
// notification.createdOn = DateUtil.convertDateToString(DateTime.now());
diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart
index 4d4ae4b5..62399abd 100644
--- a/lib/core/utils/request_utils.dart
+++ b/lib/core/utils/request_utils.dart
@@ -27,7 +27,7 @@ class RequestUtils {
}) {
bool fileNo = false;
if (nationId.isNotEmpty) {
- final numericRegex = RegExp(r'^[0-9]+$');
+ final numericRegex = RegExp(r'^[0-9]+$');
fileNo = nationId.length < 10 && numericRegex.hasMatch(nationId);
//fileNo = nationId.length < 10 && nationId.isNumericOnly() ;
if (fileNo) {
@@ -40,14 +40,23 @@ class RequestUtils {
if (zipCode == "0") {
request.patientMobileNumberOthers = phoneNumber;
} else {
- request.patientMobileNumber = int.parse(phoneNumber);
+ // Remove any non-numeric characters before parsing
+ final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
+ final parsedPhone = int.tryParse(numericPhone);
+ if (parsedPhone != null) {
+ request.patientMobileNumber = parsedPhone;
+ } else {
+ // If parsing fails, use as string in Others field
+ request.patientMobileNumberOthers = phoneNumber;
+ }
}
}
request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed
request.zipCode = zipCode; // or countryCode if defined elsewhere
if (isForRegister) {
- request.patientIdentificationID = int.parse(nationId);
+ final parsedNationId = int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), ''));
+ request.patientIdentificationID = parsedNationId ?? 0;
request.searchType = 1;
request.isHijri = calenderType.toInt;
request.patientID = patientId;
@@ -56,7 +65,7 @@ class RequestUtils {
request.isDentalAllowedBackend = false;
} else {
if (fileNo) {
- request.patientID = patientId ?? int.parse(nationId);
+ request.patientID = patientId ?? (int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0);
request.patientIdentificationID = request.nationalID;
request.searchType = 2;
} else {
@@ -71,15 +80,15 @@ class RequestUtils {
static dynamic getCommonRequestWelcome(
{required String phoneNumber,
- required OTPTypeEnum otpTypeEnum,
- required String? deviceToken,
- required bool patientOutSA,
- required String? loginTokenID,
- RegistrationDataModelPayload? registeredData,
- int? patientId,
- required String nationIdText,
- required String countryCode,
- required int loginType}) {
+ required OTPTypeEnum otpTypeEnum,
+ required String? deviceToken,
+ required bool patientOutSA,
+ required String? loginTokenID,
+ RegistrationDataModelPayload? registeredData,
+ int? patientId,
+ required String nationIdText,
+ required String countryCode,
+ required int loginType}) {
bool fileNo = false;
if (nationIdText.isNotEmpty) {
final numericRegex = RegExp(r'^[0-9]+$');
@@ -91,8 +100,17 @@ class RequestUtils {
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
} else {
- request.patientMobileNumber = int.parse(phoneNumber);
- request.mobileNo = '0$phoneNumber';
+ // Remove any non-numeric characters before parsing
+ final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
+ final parsedPhone = int.tryParse(numericPhone);
+ if (parsedPhone != null) {
+ request.patientMobileNumber = parsedPhone;
+ request.mobileNo = '0$numericPhone';
+ } else {
+ // If parsing fails, use as string in Others field
+ request.patientMobileNumberOthers = phoneNumber;
+ request.mobileNo = phoneNumber;
+ }
}
request.deviceToken = deviceToken;
request.projectOutSA = patientOutSA;
@@ -106,8 +124,8 @@ class RequestUtils {
request.searchType = registeredData.searchType != null
? registeredData.searchType
: fileNo
- ? 1
- : 2;
+ ? 1
+ : 2;
request.patientID = registeredData.patientId ?? 0;
request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0);
request.dob = registeredData.dob;
@@ -115,7 +133,8 @@ class RequestUtils {
log("nationIdText: ${nationIdText}");
} else {
if (fileNo) {
- request.patientID = patientId ?? int.parse(nationIdText);
+ final numericNationId = nationIdText.replaceAll(RegExp(r'[^0-9]'), '');
+ request.patientID = patientId ?? (int.tryParse(numericNationId) ?? 0);
request.patientIdentificationID = request.nationalID = '0';
request.searchType = 2;
//TODO: Issue HEre is Not Login
@@ -155,8 +174,17 @@ class RequestUtils {
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
} else {
- request.patientMobileNumber = int.parse(mobileNumber);
- request.mobileNo = '0$mobileNumber';
+ // Remove any non-numeric characters before parsing
+ final numericMobile = mobileNumber.replaceAll(RegExp(r'[^0-9]'), '');
+ final parsedMobile = int.tryParse(numericMobile);
+ if (parsedMobile != null) {
+ request.patientMobileNumber = parsedMobile;
+ request.mobileNo = '0$numericMobile';
+ } else {
+ // If parsing fails, use as string in Others field
+ request.patientMobileNumberOthers = mobileNumber;
+ request.mobileNo = mobileNumber;
+ }
}
}
request.projectOutSA = patientOutSA;
@@ -240,16 +268,16 @@ class RequestUtils {
"Patientobject": {
"TempValue": true,
"PatientIdentificationType": (isDubai
- ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)
- : appState.getNHICUserData.idNumber!.substring(0, 1)) ==
- "1"
+ ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)
+ : appState.getNHICUserData.idNumber!.substring(0, 1)) ==
+ "1"
? 1
: 2,
"PatientIdentificationNo":
- isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
+ isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
"MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0,
"PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
- appState.getUserRegistrationPayload.zipCode == '+966')
+ appState.getUserRegistrationPayload.zipCode == '+966')
? 0
: 1,
"FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr,
@@ -266,31 +294,31 @@ class RequestUtils {
"DateofBirthN": date,
"EmailAddress": emailAddress,
"SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
- appState.getUserRegistrationPayload.zipCode == '+966')
+ appState.getUserRegistrationPayload.zipCode == '+966')
? "1"
: "2",
"PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2),
"Marital": isDubai
? (maritalStatus == MaritalStatusTypeEnum.single
- ? '0'
- : maritalStatus == MaritalStatusTypeEnum.married
- ? '1'
- : '2')
+ ? '0'
+ : maritalStatus == MaritalStatusTypeEnum.married
+ ? '1'
+ : '2')
: (appState.getNHICUserData.maritalStatusCode == 'U'
- ? '0'
- : appState.getNHICUserData.maritalStatusCode == 'M'
- ? '1'
- : '2'),
+ ? '0'
+ : appState.getNHICUserData.maritalStatusCode == 'M'
+ ? '1'
+ : '2'),
},
"PatientIdentificationID":
- isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
+ isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
"PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0'
? appState.getUserRegistrationPayload.patientMobileNumber
: '0${appState.getUserRegistrationPayload.patientMobileNumber}',
"DOB": dob,
"IsHijri": appState.getUserRegistrationPayload.isHijri,
"PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
- appState.getUserRegistrationPayload.zipCode == '+966')
+ appState.getUserRegistrationPayload.zipCode == '+966')
? 0
: 1,
"isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend,
@@ -314,7 +342,9 @@ class RequestUtils {
request.sharedPatientId = 0;
request.sharedPatientIdentificationId = nationalIDorFile;
} else if (loginType == 2) {
- request.sharedPatientId = int.parse(nationalIDorFile);
+ // Remove any non-numeric characters before parsing
+ final numericId = nationalIDorFile.replaceAll(RegExp(r'[^0-9]'), '');
+ request.sharedPatientId = int.tryParse(numericId) ?? 0;
request.sharedPatientIdentificationId = '';
}
request.searchType = loginType;
@@ -325,4 +355,4 @@ class RequestUtils {
request.isDentalAllowedBackend = false;
return request;
}
-}
+}
\ No newline at end of file
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index 320bd1cf..b4e91854 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -851,7 +851,7 @@ class Utils {
/// Widget to build an image from network - automatically preserves colors in visual modes
static Widget buildImgWithNetwork({
- required String url,
+ required String? url,
required Color iconColor,
bool isDisabled = false,
double? width,
@@ -859,6 +859,13 @@ class Utils {
BoxFit fit = BoxFit.cover,
ImageErrorWidgetBuilder? errorBuilder,
}) {
+ if (url == null || url.isEmpty) {
+ return Utils.buildSvgWithAssets(
+ width: width ?? 24.w,
+ height: height ?? 24.h,
+ icon: AppAssets.noImageFound,
+ );
+ }
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
return PreserveImageColors(
@@ -870,7 +877,7 @@ class Utils {
errorBuilder: errorBuilder ??
(_, __, ___) {
//todo_section change the error builder icon that it is returning
- return Utils.buildSvgWithAssets(width: iconW, height: iconH, icon: AppAssets.no_visit_icon);
+ return Utils.buildSvgWithAssets(width: iconW, height: iconH, icon: AppAssets.noImageFound);
},
),
);
diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart
index c355819e..291e4a9b 100644
--- a/lib/features/authentication/authentication_repo.dart
+++ b/lib/features/authentication/authentication_repo.dart
@@ -147,6 +147,9 @@ class AuthenticationRepoImp implements AuthenticationRepo {
bool isRegister = false,
bool isFormFamilyFile = false,
}) async {
+
+
+
int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1;
sendActivationCodeReq.patientOutSA = isOutKsa;
sendActivationCodeReq.isDentalAllowedBackend = false;
diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart
index b51b36e4..7688cfa4 100644
--- a/lib/features/authentication/authentication_view_model.dart
+++ b/lib/features/authentication/authentication_view_model.dart
@@ -3,6 +3,7 @@ import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:get_it/get_it.dart';
@@ -42,6 +43,7 @@ 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/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
+import 'package:hmg_patient_app_new/core/services/turnstile_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@@ -60,6 +62,7 @@ class AuthenticationViewModel extends ChangeNotifier {
final DialogService _dialogService;
final NavigationService _navigationService;
final LocalAuthService _localAuthService;
+ final TurnstileService _turnstileService;
AuthenticationViewModel({
required AppState appState,
@@ -69,12 +72,14 @@ class AuthenticationViewModel extends ChangeNotifier {
required NavigationService navigationService,
required CacheService cacheService,
required LocalAuthService localAuthService,
+ required TurnstileService turnstileService,
}) : _navigationService = navigationService,
_dialogService = dialogService,
_errorHandlerService = errorHandlerService,
_appState = appState,
_authenticationRepo = authenticationRepo,
- _localAuthService = localAuthService {}
+ _localAuthService = localAuthService,
+ _turnstileService = turnstileService {}
final TextEditingController nationalIdController = TextEditingController(),
phoneNumberController = TextEditingController(),
dobController = TextEditingController(),
@@ -974,6 +979,17 @@ class AuthenticationViewModel extends ChangeNotifier {
// TODO: GET APP SMS SIGNATURE HERE
request.sMSSignature = await getSignature();
+
+ // if(!kDebugMode) {
+ // // Get Turnstile token if configured
+ // final turnstileToken = await _turnstileService.getTurnstileToken();
+ // if (turnstileToken != null) {
+ // request.turnstileToken = turnstileToken;
+ // } else {
+ // request.turnstileToken = "";
+ // }
+ // }
+
if (isNeedLoading) {
LoaderBottomSheet.showLoader();
}
@@ -985,7 +1001,10 @@ class AuthenticationViewModel extends ChangeNotifier {
sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isFormFamilyFile: isFormFamilyFile);
resultEither.fold(
- (failure) async => await _errorHandlerService.handleError(failure: failure),
+ (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await _errorHandlerService.handleError(failure: failure);
+ },
(apiResponse) async {
if (isFormFamilyFile) {
if (apiResponse.data['PatientShareRequestID'] != null && apiResponse.data['PatientShareRequestID'] == 0 && apiResponse.messageStatus == 1) {
diff --git a/lib/features/authentication/models/request_models/send_activation_request_model.dart b/lib/features/authentication/models/request_models/send_activation_request_model.dart
index 2908f7ec..6e2ddb93 100644
--- a/lib/features/authentication/models/request_models/send_activation_request_model.dart
+++ b/lib/features/authentication/models/request_models/send_activation_request_model.dart
@@ -30,6 +30,7 @@ class SendActivationRequest {
int? status;
int? familyRegionID;
bool? isPatientExcluded;
+ String? turnstileToken;
SendActivationRequest(
{this.patientMobileNumber,
@@ -62,7 +63,8 @@ class SendActivationRequest {
this.responseID,
this.status,
this.familyRegionID,
- this.isPatientExcluded});
+ this.isPatientExcluded,
+ this.turnstileToken});
SendActivationRequest.fromJson(Map json) {
patientMobileNumber = json['PatientMobileNumber'];
@@ -96,6 +98,7 @@ class SendActivationRequest {
status = json['Status'];
familyRegionID = json['FamilyRegionID'];
isPatientExcluded = json['IsPatientExcluded'];
+ turnstileToken = json['TurnstileToken'];
}
Map toJson() {
@@ -135,6 +138,9 @@ class SendActivationRequest {
data['Status'] = status;
data['FamilyRegionID'] = familyRegionID;
data['IsPatientExcluded'] = isPatientExcluded;
+ if (turnstileToken != null) {
+ data['TurnstileToken'] = turnstileToken;
+ }
return data;
}
}
diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart
index e4f028d9..4831901d 100644
--- a/lib/features/book_appointments/book_appointments_view_model.dart
+++ b/lib/features/book_appointments/book_appointments_view_model.dart
@@ -404,6 +404,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
isWaitingAppointmentSelected = false;
isPatientRescheduleAppointment = false;
bodyTypes = [maleLaserCategory, femaleLaserCategory];
+ selectedChiefComplaintID = 0;
// getLocation();
notifyListeners();
}
@@ -420,14 +421,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
setIsNearestAppointmentSelected(bool isNearestAppointmentSelected) {
this.isNearestAppointmentSelected = isNearestAppointmentSelected;
if (isNearestAppointmentSelected) {
- for (var group in doctorsListGrouped) {
- group.sort((a, b) {
- var aSlot = a.nearestFreeSlot;
- var bSlot = b.nearestFreeSlot;
- if (aSlot == null || bSlot == null) return 0;
- return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot));
- });
- }
+ // for (var group in doctorsListGrouped) {
+ // group.sort((a, b) {
+ // var aSlot = a.nearestFreeSlot;
+ // var bSlot = b.nearestFreeSlot;
+ // if (aSlot == null || bSlot == null) return 0;
+ // return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot));
+ // });
+ // }
} else {
// for (var group in doctorsListGrouped) {
// group.sort((a, b) {
@@ -675,7 +676,6 @@ class BookAppointmentsViewModel extends ChangeNotifier {
(failure) async {
isDoctorsListLoading = false;
if (onError != null) onError(LocaleKeys.noDoctorFound.tr());
-
notifyListeners();
},
(apiResponse) {
@@ -690,7 +690,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clearSearchFilters();
getFiltersFromDoctorList();
_groupDoctorsList();
- setIsNearestAppointmentSelected(isNearest);
+ // setIsNearestAppointmentSelected(isNearest);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart
index 1b460a03..15f937e9 100644
--- a/lib/features/contact_us/contact_us_view_model.dart
+++ b/lib/features/contact_us/contact_us_view_model.dart
@@ -12,8 +12,22 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_p
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_status_coc_response_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/services/error_handler_service.dart';
+import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:permission_handler/permission_handler.dart';
+/// Class to store feedback attachment data
+class FeedbackAttachment {
+ final String base64String;
+ final File file;
+ final String fileName;
+
+ FeedbackAttachment({
+ required this.base64String,
+ required this.file,
+ required this.fileName,
+ });
+}
+
class ContactUsViewModel extends ChangeNotifier {
ContactUsRepo contactUsRepo;
ErrorHandlerService errorHandlerService;
@@ -34,7 +48,7 @@ class ContactUsViewModel extends ChangeNotifier {
String? chatRequestID;
- List feedbackAttachmentList = [];
+ List feedbackAttachmentList = [];
List cocItemsList = [];
bool isCOCItemsListLoading = false;
@@ -167,13 +181,20 @@ class ContactUsViewModel extends ChangeNotifier {
notifyListeners();
}
- addFeedbackAttachment(String attachmentPath) {
- feedbackAttachmentList.add(attachmentPath);
+ addFeedbackAttachment(String base64String, File file) {
+ // Extract filename from file path
+ final fileName = file.path.split('/').last;
+
+ feedbackAttachmentList.add(FeedbackAttachment(
+ base64String: base64String,
+ file: file,
+ fileName: fileName,
+ ));
notifyListeners();
}
- removeFeedbackAttachment(String attachmentPath) {
- feedbackAttachmentList.remove(attachmentPath);
+ removeFeedbackAttachment(FeedbackAttachment attachment) {
+ feedbackAttachmentList.remove(attachment);
notifyListeners();
}
@@ -281,7 +302,7 @@ class ContactUsViewModel extends ChangeNotifier {
Future insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async {
RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem();
- requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : "";
+ requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first.base64String : "";
requestInsertCOCItem.title = subject;
requestInsertCOCItem.details = message;
requestInsertCOCItem.cOCTypeName = selectedFeedbackType.id.toString();
@@ -290,7 +311,7 @@ class ContactUsViewModel extends ChangeNotifier {
requestInsertCOCItem.isUserLoggedIn = true;
requestInsertCOCItem.projectID = 0;
requestInsertCOCItem.patientName = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}";
- requestInsertCOCItem.fileName = "Image 1";
+ requestInsertCOCItem.fileName = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first.fileName : "";
requestInsertCOCItem.appVersion = ApiConsts.appVersionID;
requestInsertCOCItem.uILanguage = appState.isArabic() ? "ar" : "en";
requestInsertCOCItem.browserInfo = Platform.localHostname;
@@ -308,7 +329,15 @@ class ContactUsViewModel extends ChangeNotifier {
final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await errorHandlerService.handleError(failure: failure);
+ },
+ // (failure) async {
+ // if(onError != null) {
+ // onError(failure.toString());
+ // }
+ // },
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart
index 621ebe4c..2422920a 100644
--- a/lib/features/insurance/insurance_view_model.dart
+++ b/lib/features/insurance/insurance_view_model.dart
@@ -33,14 +33,10 @@ class InsuranceViewModel extends ChangeNotifier {
InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService});
initInsuranceProvider() {
- debugPrint("InsuranceViewModel: initInsuranceProvider called, isInsuranceDataToBeLoaded=$isInsuranceDataToBeLoaded");
if (isInsuranceDataToBeLoaded) {
patientInsuranceList.clear();
isInsuranceLoading = true;
- debugPrint("InsuranceViewModel: Calling getPatientInsuranceDetails API");
getPatientInsuranceDetails();
- } else {
- debugPrint("InsuranceViewModel: Skipping API call, isInsuranceDataToBeLoaded is false");
}
patientInsuranceCardHistoryList.clear();
isInsuranceHistoryLoading = true;
@@ -82,17 +78,14 @@ class InsuranceViewModel extends ChangeNotifier {
Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isInsuranceDataToBeLoaded) {
- debugPrint("InsuranceViewModel: getPatientInsuranceDetails - skipping, flag is false");
return;
}
- debugPrint("InsuranceViewModel: getPatientInsuranceDetails - making API call");
final result = await insuranceRepo.getPatientInsuranceDetails();
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
- debugPrint("InsuranceViewModel: API call failed - ${failure.toString()}");
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = true;
isInsuranceExpired = false;
@@ -102,11 +95,9 @@ class InsuranceViewModel extends ChangeNotifier {
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
- debugPrint("InsuranceViewModel: API returned error status");
isInsuranceDataToBeLoaded = false;
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
- debugPrint("InsuranceViewModel: API success - received ${apiResponse.data?.length ?? 0} insurance records");
patientInsuranceList = apiResponse.data!;
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = false;
@@ -115,7 +106,6 @@ class InsuranceViewModel extends ChangeNotifier {
isInsuranceExpired = DateTime.now().isAfter(
DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo),
);
- debugPrint("InsuranceViewModel: Insurance card expired: $isInsuranceExpired");
}
isInsuranceExpiryBannerShown = isInsuranceExpired;
diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart
index 82468ada..94679047 100644
--- a/lib/features/lab/lab_repo.dart
+++ b/lib/features/lab/lab_repo.dart
@@ -29,6 +29,7 @@ abstract class LabRepo {
Future>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload});
Future>> getPatientAiOverViewLabOrder(var payloa);
+ Map> groupLabResultsByPackageName(List labResults);
}
class LabRepoImp implements LabRepo {
@@ -37,6 +38,55 @@ class LabRepoImp implements LabRepo {
LabRepoImp({required this.loggerService, required this.apiClient});
+ /// Groups a list of LabResult objects by their PackageName
+ /// Returns a Map where the key is the PackageName and the value is a List of LabResult objects
+ /// Both the keys and the lists are sorted alphabetically
+ ///
+ /// Example usage:
+ /// ```dart
+ /// List labResults = [/* your lab results */];
+ /// Map> groupedResults = labRepo.groupLabResultsByPackageName(labResults);
+ ///
+ /// // Access results by package name
+ /// groupedResults.forEach((packageName, results) {
+ /// print('Package: $packageName has ${results.length} results');
+ /// });
+ /// ```
+ Map> groupLabResultsByPackageName(List labResults) {
+ Map> groupedResults = {};
+
+ // Group results by package name
+ for (var labResult in labResults) {
+ String packageName = labResult.PackageName ?? 'Unknown Package';
+
+ if (groupedResults.containsKey(packageName)) {
+ // Package name exists, add to existing list
+ groupedResults[packageName]!.add(labResult);
+ } else {
+ // Package name doesn't exist, create new key with new list
+ groupedResults[packageName] = [labResult];
+ }
+ }
+
+ // Sort each list alphabetically by description
+ groupedResults.forEach((key, list) {
+ list.sort((a, b) {
+ String descA = (a.description ?? '').toLowerCase();
+ String descB = (b.description ?? '').toLowerCase();
+ return descA.compareTo(descB);
+ });
+ });
+
+ // Sort the map keys alphabetically and return a new sorted map
+ var sortedKeys = groupedResults.keys.toList()..sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
+ Map> sortedGroupedResults = {};
+ for (var key in sortedKeys) {
+ sortedGroupedResults[key] = groupedResults[key]!;
+ }
+
+ return sortedGroupedResults;
+ }
+
@override
Future>>> getPatientLabOrders() async {
Map mapDevice = {};
diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart
index 09caf578..1c25ef9d 100644
--- a/lib/features/lab/lab_view_model.dart
+++ b/lib/features/lab/lab_view_model.dart
@@ -57,7 +57,11 @@ class LabViewModel extends ChangeNotifier {
PatientLabOrdersResponseModel? currentlySelectedPatientOrder;
- List mainLabResultsByHospitals = [];
+ // Flat list for UI compatibility
+ List mainLabResultsByHospitalsList = [];
+
+ // Grouped map by package name for efficient lookups
+ Map> mainLabResultsByPackageName = {};
List mainLabResults = [];
List mainGraphPoints = [];
@@ -383,7 +387,8 @@ class LabViewModel extends ChangeNotifier {
Future getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true;
notifyListeners();
- mainLabResultsByHospitals.clear();
+ mainLabResultsByHospitalsList.clear();
+ mainLabResultsByPackageName.clear();
final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
@@ -397,7 +402,9 @@ class LabViewModel extends ChangeNotifier {
isLabResultByHospitalLoading = false;
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
- mainLabResultsByHospitals = apiResponse.data ?? [];
+ mainLabResultsByHospitalsList = apiResponse.data ?? [];
+ // Group the results by package name for efficient lookups
+ mainLabResultsByPackageName = labRepo.groupLabResultsByPackageName(mainLabResultsByHospitalsList);
notifyListeners();
}
},
@@ -842,6 +849,25 @@ class LabViewModel extends ChangeNotifier {
notifyListeners();
}
+ // /// Finds a matching LabResult from the grouped map by package name
+ // /// Searches through all packages to find a result matching the test code or description
+ // LabResult? _findMatchingLabResult(TestDetails testDetail) {
+ // // Search through all packages in the grouped map
+ // for (var packageResults in mainLabResultsByPackageName.values) {
+ // try {
+ // var matchingResult = packageResults.firstWhere(
+ // (labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description,
+ // );
+ // return matchingResult;
+ // } catch (e) {
+ // // Continue searching in other packages
+ // continue;
+ // }
+ // }
+ // // Return null if no match found
+ // return LabResult();
+ // }
+
Future getAiOverviewLabOrders({required int langId, required PatientLabOrdersResponseModel labOrder, required String loadingText}) async {
isLabAIAnalysisNeedsToBeShown = true;
notifyListeners();
@@ -858,7 +884,7 @@ class LabViewModel extends ChangeNotifier {
if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) {
for (var testDetail in labOrder.testDetails!) {
LabResult? matchingResult =
- mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult());
+ mainLabResultsByHospitalsList.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult());
testResults.add({
"test_name": testDetail.description ?? "",
"result_value": matchingResult.resultValue ?? "",
diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart
index 2808dce9..78be004a 100644
--- a/lib/features/lab/models/resp_models/lab_result.dart
+++ b/lib/features/lab/models/resp_models/lab_result.dart
@@ -28,6 +28,7 @@ class LabResult {
String? packageShortDescription;
int? testId;
String? testShortDescription;
+ String? PackageName;
LabResult(
{this.description,
@@ -56,6 +57,7 @@ class LabResult {
this.referenceLow,
this.testId,
this.testShortDescription,
+ this.PackageName
});
LabResult.fromJson(Map json) {
@@ -88,6 +90,7 @@ class LabResult {
resultTypeID = json['ResultTypeID'];
testId = json['TestID'];
testShortDescription = json['TestShortDescription'];
+ PackageName = json['PackageName'];
}
Map toJson() {
@@ -118,6 +121,7 @@ class LabResult {
data['ReferenceLow'] = this.referenceLow;
data['TestID'] = this.testId;
data['TestShortDescription'] = this.testShortDescription;
+ data['PackageName'] = this.PackageName;
return data;
}
diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart
index 41a726d9..6fb8c8a2 100644
--- a/lib/features/medical_file/medical_file_view_model.dart
+++ b/lib/features/medical_file/medical_file_view_model.dart
@@ -29,7 +29,11 @@ class MedicalFileViewModel extends ChangeNotifier {
bool isPatientSickLeavePDFLoading = false;
bool isPatientMedicalReportsListLoading = false;
bool isPatientAllergiesListLoading = false;
+ bool _isFamilyFilesLoading = false;
+ bool get isFamilyFilesLoading => _isFamilyFilesLoading;
+
+ set setIsFamilyFilesLoading(bool value) => _isFamilyFilesLoading = value;
MedicalFileRepo medicalFileRepo;
ErrorHandlerService errorHandlerService;
@@ -42,7 +46,7 @@ class MedicalFileViewModel extends ChangeNotifier {
bool isSickLeavesSortByClinic = true;
- // bool isSickLeavesDataNeedsReloading = true;
+ bool isSickLeavesDataNeedsReloading = true;
List patientAllergiesList = [];
@@ -165,15 +169,15 @@ class MedicalFileViewModel extends ChangeNotifier {
}
setIsPatientSickLeaveListLoading(bool val) {
- // if (val && isSickLeavesDataNeedsReloading) {
- patientSickLeaveList.clear();
- patientSickLeavesByClinic.clear();
+ if (val && isSickLeavesDataNeedsReloading) {
+ patientSickLeaveList.clear();
+ patientSickLeavesByClinic.clear();
patientSickLeavesByHospital.clear();
patientSickLeavesViewList.clear();
patientSickLeavePDFBase64 = "";
isSickLeavesSortByClinic = true;
isPatientSickLeaveListLoading = val;
- // }
+ }
notifyListeners();
}
@@ -282,9 +286,9 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
- // if (!isSickLeavesDataNeedsReloading) {
- // return;
- // }
+ if (!isSickLeavesDataNeedsReloading) {
+ return;
+ }
patientSickLeaveList.clear();
notifyListeners();
@@ -300,6 +304,7 @@ class MedicalFileViewModel extends ChangeNotifier {
// ),
(failure) async {
isPatientSickLeaveListLoading = false;
+ isSickLeavesDataNeedsReloading = true;
notifyListeners();
},
(apiResponse) {
@@ -330,7 +335,7 @@ class MedicalFileViewModel extends ChangeNotifier {
}
}
patientSickLeavesViewList = patientSickLeavesByClinic;
- // isSickLeavesDataNeedsReloading = false;
+ isSickLeavesDataNeedsReloading = false;
notifyListeners();
if (onSuccess != null) {
@@ -430,21 +435,42 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future getFamilyFiles({int? status, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+
+ setIsFamilyFilesLoading = true;
+
+ final shouldShowLoader = patientFamilyFiles.isEmpty;
+ if (shouldShowLoader) {
+ LoaderBottomSheet.showLoader();
+ }
+
final result = await medicalFileRepo.getPatientFamilyFiles(status, _appState.getSuperUserID != null ? _appState.getSuperUserID! : _appState.getAuthenticatedUser()!.patientId!);
result.fold(
- (failure) async => await errorHandlerService.handleError(
- failure: failure,
- onOkPressed: () {
- onError!(failure.message);
- },
- ),
+ (failure) async {
+
+ setIsFamilyFilesLoading = false;
+
+ await errorHandlerService.handleError(
+ failure: failure,
+ onOkPressed: () {
+ if (shouldShowLoader) {
+ LoaderBottomSheet.hideLoader();
+ }
+ onError!(failure.message);
+ },
+ );
+ },
(apiResponse) {
if (apiResponse.messageStatus == 2) {
+
+ setIsFamilyFilesLoading = false;
+
// _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {});
+ if (shouldShowLoader) {
+ LoaderBottomSheet.hideLoader();
+ }
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null) {
- // Add current user as the first active family file
final currentUser = _appState.getAuthenticatedUser()!;
final currentUserFamilyFile = FamilyFileResponseModelLists(
patientId: currentUser.patientId,
@@ -524,6 +550,12 @@ class MedicalFileViewModel extends ChangeNotifier {
pendingFamilyFiles = newPendingFamilyFiles;
}
+ // Clear loading state on success
+ setIsFamilyFilesLoading = false;
+
+ if (shouldShowLoader) {
+ LoaderBottomSheet.hideLoader();
+ }
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@@ -661,13 +693,10 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future handleFamilyFileRequestOTPVerification() async {
- LoaderBottomSheet.showLoader();
if (!_appState.getIsChildLoggedIn) {
await getFamilyFiles(status: 0);
await getAllPendingRecordsByResponseId();
}
-
- LoaderBottomSheet.hideLoader();
}
Future removeFileFromFamilyMembers({int? id}) async {
diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart
index d7a025fc..87fb2ec2 100644
--- a/lib/features/my_appointments/my_appointments_repo.dart
+++ b/lib/features/my_appointments/my_appointments_repo.dart
@@ -25,7 +25,7 @@ import 'models/resp_models/appointment_details_resp_model.dart';
abstract class MyAppointmentsRepo {
Future>>> getPatientAppointments(
- {required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false});
+ {required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false});
Future>> getPatientShareAppointment(
{required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment});
@@ -53,7 +53,7 @@ abstract class MyAppointmentsRepo {
Future>>> getPatientAppointmentsForTimeLine();
- Future>>> getPatientDoctorsList();
+ Future>>> getPatientDoctorsList({bool isTop8 = false});
Future>>> getFavouriteDoctorsList();
@@ -105,14 +105,15 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@override
Future>>> getPatientAppointments(
- {required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async {
+ {required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false}) async {
Map mapDevice = {
"IsActiveAppointment": isActiveAppointment,
"IsComingFromCOC": false,
"isForUpcomming": false,
"IsForMedicalReport": false,
"IsForArrived": isArrivedAppointments,
- "IsIrisPrescription": isForEyeMeasurement
+ "IsIrisPrescription": isForEyeMeasurement,
+ "IsForTimeLine": isForTimeLine
};
try {
@@ -172,9 +173,9 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
try {
final list = response['OnlineCheckInAppointments'];
patientShareObj = (list == null || list.isEmpty) ? PatientAppointmentShareResponseModel() : PatientAppointmentShareResponseModel.fromJson(list[0]);
- patientShareObj.isCash = response["IsCash"];
- patientShareObj.isEligible = response["IsEligible"];
- patientShareObj.isInsured = response["IsInsured"];
+ patientShareObj.isCash = isLiveCareAppointment ? list[0]["IsCash"] : response["IsCash"];
+ patientShareObj.isEligible = isLiveCareAppointment ? list[0]["IsEligible"] : response["IsEligible"];
+ patientShareObj.isInsured = isLiveCareAppointment ? list[0]["IsInsured"] : response["IsInsured"];
// if (list == null || list.isEmpty) {
// throw Exception("patient share list is empty");
@@ -514,11 +515,12 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
}
@override
- Future>>> getPatientDoctorsList() async {
+ Future>>> getPatientDoctorsList({bool isTop8 = false}) async {
Map mapDevice = {
"Top": 0,
"beforeDays": 0,
"exludType": 4,
+ "IsTop8": isTop8,
};
try {
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index 9182f9e7..832d94fe 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -54,6 +54,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isFavouriteDoctorsDataFetched = false;
bool isAppointmentDataToBeLoaded = true;
+ bool isMyDoctorsDataToBeLoaded = true;
+ bool isArrivedAppointmentDataLoaded = false;
+ bool isFullArrivedAppointmentsLoaded = false;
bool isEyeMeasurementsAppointmentsLoading = false;
@@ -75,7 +78,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List patientUpcomingAppointmentsHistoryList = [];
List patientArrivedAppointmentsHistoryList = [];
- List patientTimelineAppointmentsList = [];
+ // List patientAllArrivedAppointmentsHistoryList = [];
List patientMyDoctorsList = [];
@@ -171,11 +174,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
- patientTimelineAppointmentsList.clear();
+ // patientAllArrivedAppointmentsHistoryList.clear();
patientEyeMeasurementsAppointmentsHistoryList.clear();
isMyAppointmentsLoading = true;
isTimeLineAppointmentsLoading = true;
patientMyDoctorsList.clear();
+ isFullArrivedAppointmentsLoaded = false;
}
isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true;
@@ -210,9 +214,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
+ setIsMyDoctorsDataToBeLoaded(bool val) {
+ isMyDoctorsDataToBeLoaded = val;
+ notifyListeners();
+ }
+
setIsAppointmentDataToBeLoaded(bool val) {
if (val) {
isMyAppointmentsLoading = true;
+ isMyDoctorsDataToBeLoaded = true;
}
isAppointmentDataToBeLoaded = val;
notifyListeners();
@@ -259,7 +269,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
- Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {bool isForTimeLine = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
isPatientHasQueueAppointment = false;
notifyListeners();
@@ -268,15 +278,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
getPatientAppointmentQueueDetails();
}
}
- if (!isAppointmentDataToBeLoaded) return;
- debugPrint('getPatientAppointments CALLED!!!');
+ // Skip API call if full data is already loaded and we're not requesting timeline view
+ if (!isForTimeLine && isFullArrivedAppointmentsLoaded && !isAppointmentDataToBeLoaded) {
+ return;
+ }
+
+ if (!isAppointmentDataToBeLoaded) return;
patientAppointmentsByClinic.clear();
patientAppointmentsByHospital.clear();
patientAppointmentsViewList.clear();
- patientTimelineAppointmentsList.clear();
+ // patientAllArrivedAppointmentsHistoryList.clear();
filteredAppointmentList.clear();
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
@@ -285,7 +299,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
final results = await Future.wait([
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments),
- myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true),
+ myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: isForTimeLine),
]);
results[0].fold(
@@ -314,6 +328,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientArrivedAppointmentsHistoryList = apiResponse.data!;
isMyAppointmentsLoading = false;
isAppointmentDataToBeLoaded = false;
+ if (!isForTimeLine) {
+ // patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
+ isArrivedAppointmentDataLoaded = true;
+ isFullArrivedAppointmentsLoaded = true;
+ }
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@@ -340,13 +359,73 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
// }
- debugPrint('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
- debugPrint('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
- debugPrint('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
notifyListeners();
}
+ changeTabToArrived() {
+ if (isFullArrivedAppointmentsLoaded && patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
+ selectedTabIndex = 1;
+ updateListWRTTab(1);
+ notifyListeners();
+ }
+ }
+
+ /// Loads full arrived appointments list if not already loaded
+ /// This is called when navigating to My Appointments page from landing page
+ Future loadFullArrivedAppointmentsIfNeeded({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ // Skip if already loaded
+ if (isFullArrivedAppointmentsLoaded) {
+ // Auto-switch to Arrived tab if no upcoming appointments (even when data is already loaded)
+ if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
+ selectedTabIndex = 1;
+ updateListWRTTab(1);
+ notifyListeners();
+ }
+ return;
+ }
+
+ setIsAppointmentsHistoryLoading(true);
+
+ // Fetch full arrived appointments (isForTimeLine = false)
+ final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: false);
+
+ result.fold(
+ (failure) async => await errorHandlerService.handleError(failure: failure),
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // Handle error
+ } else if (apiResponse.messageStatus == 1) {
+ patientArrivedAppointmentsHistoryList = apiResponse.data!;
+ // patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
+ isArrivedAppointmentDataLoaded = true;
+ isFullArrivedAppointmentsLoaded = true;
+
+ // Rebuild the combined list
+ patientAppointmentsHistoryList.clear();
+ patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList);
+ patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
+
+ isMyAppointmentsLoading = false;
+
+ // Auto-switch to Arrived tab if no upcoming appointments
+ if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
+ selectedTabIndex = 1;
+ updateListWRTTab(1);
+ } else {
+ // Update filtered list based on current tab
+ updateListWRTTab(selectedTabIndex);
+ }
+
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
void getFiltersForSelectedAppointmentList(List filteredAppointmentList) {
availableFilters.clear();
if (filteredAppointmentList.isEmpty == true) return;
@@ -355,9 +434,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
availableFilters.add(AppointmentListingFilters.LIVECARE);
}
- if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
- availableFilters.add(AppointmentListingFilters.WALKIN);
- }
+ // if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
+ // availableFilters.add(AppointmentListingFilters.WALKIN);
+ // }
if (filteredAppointmentList.any((element) => AppointmentType.isArrived(element) == true)) {
availableFilters.add(AppointmentListingFilters.ARRIVED);
@@ -673,13 +752,13 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
- Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async {
- // if (!isAppointmentDataToBeLoaded) return;
+ Future getPatientMyDoctors({bool isTop8 = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ if (!isMyDoctorsDataToBeLoaded) return;
isPatientMyDoctorsLoading = true;
patientMyDoctorsList.clear();
notifyListeners();
- final result = await myAppointmentsRepo.getPatientDoctorsList();
+ final result = await myAppointmentsRepo.getPatientDoctorsList(isTop8: isTop8);
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
@@ -692,6 +771,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
patientMyDoctorsList = apiResponse.data!;
isPatientMyDoctorsLoading = false;
+ isMyDoctorsDataToBeLoaded = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@@ -793,15 +873,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
selectedFilter = [];
// if(previouslySelectedTab == selectedTabIndex ) return;
switch (index) {
+ // case 0:
+ // filteredAppointmentList.clear();
+ // filteredAppointmentList.addAll(patientAppointmentsHistoryList);
+ // break;
case 0:
- filteredAppointmentList.clear();
- filteredAppointmentList.addAll(patientAppointmentsHistoryList);
- break;
- case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList);
break;
- case 2:
+ case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList);
break;
@@ -827,11 +907,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
this.end = end;
isDateFilterSelected = true;
List sourceList = [];
- if (selectedTabIndex == 0) {
- sourceList = patientAppointmentsHistoryList;
- } else if (selectedTabIndex == 1) {
+ // if (selectedTabIndex == 0) {
+ // sourceList = patientAppointmentsHistoryList;
+ // } else
+ if (selectedTabIndex == 0) {
sourceList = patientUpcomingAppointmentsHistoryList;
- } else if (selectedTabIndex == 2) {
+ } else if (selectedTabIndex == 1) {
sourceList = patientArrivedAppointmentsHistoryList;
}
// if (isDateFilterSelected) sourceList = filteredAppointmentList;
@@ -1291,7 +1372,6 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
} catch (e) {
// Ignore errors if reminder doesn't exist
- debugPrint("No existing ${type.displayName} to remove");
}
}
}
diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart
index 35a924af..508753ef 100644
--- a/lib/features/paytabs/paytabs_view_model.dart
+++ b/lib/features/paytabs/paytabs_view_model.dart
@@ -11,17 +11,29 @@ import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.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/enums.dart';
import 'package:hmg_patient_app_new/core/paytabs_config.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_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/payfort_repo.dart';
+import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class PayTabsViewModel extends ChangeNotifier {
late PaymentSdkConfigurationDetails paymentConfiguration;
final AppState appState;
+ final PayfortRepo payfortRepo;
+ final ErrorHandlerService errorHandlerService;
late PaytabsTransactionResponseModel paytabsTransactionResponseModel;
PayTabsViewModel({
required this.appState,
+ required this.payfortRepo,
+ required this.errorHandlerService,
});
setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
@@ -152,6 +164,42 @@ class PayTabsViewModel extends ChangeNotifier {
notifyListeners();
}
+ Future paytabsRequestInsert({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) {
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future paytabsResponseInsert({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) {
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
@@ -189,41 +237,132 @@ class PayTabsViewModel extends ChangeNotifier {
});
}
- startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
+ startCardPayment({
+ required String paymentOption,
+ required String liveServiceID,
+ required String isScheduleLiveCare,
+ required String appointmentDate,
+ required String appointmentNo,
+ required String doctorID,
+ required String transactionID,
+ required String amount,
+ required String fileNumber,
+ required String orderDescription,
+ required String serviceID,
+ required String projectID,
+ Function(PaytabsTransactionResponseModel)? onSuccess,
+ Function(String)? onError,
+ }) async {
bool _callbackFired = false; // prevent duplicate callback invocations
- FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) {
- if (_callbackFired) return; // ignore any subsequent callback calls
+ try {
+ LoaderBottomSheet.showLoader();
- final transactionDetails = event["data"] as Map