diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index dab50baa..6ba4a959 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1780,11 +1780,11 @@ "illurgyInfomation": "الحساسية", "vaccineInfomation": "التطعيمات", "approvals1": "موافقات", - "convertBloodcholesterolInfo":"حول قيمة كوليسترول الدم بين ملجم/ديسيلتر و مليمول/لتر", + "convertBloodcholesterolInfo": "حول قيمة كوليسترول الدم بين ملجم/ديسيلتر و مليمول/لتر", "carbsAlert": "حساب نسب المغذيات (الكربوهيدرات ،البروتين والدهون) بالسعرات الحرارية والقرامات وفقا لنسب محددة مسبقا", "bodyFatAlert": "يحسب بناءً على الحجم", "ibwAlert": "حساب الوزن المثالي بناءً على الطول، الوزن، وبنية الجسم", - "glucoseAlert":"تغيير وحدات القياس بين مليجرام لكل ديسيلتر و ملي مول لكل لتر", + "glucoseAlert": "تغيير وحدات القياس بين مليجرام لكل ديسيلتر و ملي مول لكل لتر", "selectBloodGroup": "اختر الفصيلة", "routePrescription": "طريقة الاستخدام", "noNearestER": "لا يوجد طوارئ قريب منك", @@ -1820,9 +1820,9 @@ "dragToMoveAround": "اسحب للتحرك", "startExploring": "ابدأ الاستكشاف", "symptomsCheckSuggestions": "الاحتمالات", - "appointmentArrived": "تم الحضور", + "appointmentArrived": "تم الحضور", "calendarPermissionAlertForAppointment": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير الموعد", -"rechargePageKey": "شحن المحفظة", + "rechargePageKey": "شحن المحفظة", "selectFamilyFile": "اختيار ", "ancillaryOrdersListNew": "طلبات غير مكتملة", "medicalReportNew": "التقارير الطبية", @@ -1845,5 +1845,6 @@ "maxOneFileAllowed": "يمكن رفع ملف واحد فقط", "fileSizeExceedsLimit": "يجب ألا يتجاوز حجم الملف 1 ميغابايت", "completedPrescriptionOrder": "تمت الخدمة", - "submitEReferral": "تقديم الطلب" + "submitEReferral": "تقديم الطلب", + "requestRefund": "طلب استرداد" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b4dd35f7..312a8121 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -883,7 +883,7 @@ "selectDate": "Select Date", "historyFlowchart": "History FlowChart", "to": "to", - "startDate" : "Start Date", + "startDate": "Start Date", "endDate": "End Date", "hmgHospitals": "HMG Hospitals", "hmcMedicalClinic": "HMC Medical Centers", @@ -896,7 +896,7 @@ "walkin": "Booked", "continueString": "Continue", "laserClinic": "Laser Clinic", - "covid_info" :"Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", + "covid_info": "Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", "appointmentDetails": "Appointment Details", "checkingDoctorAvailability": "Checking doctor availability...", "cancellingAppointmentPleaseWait": "Cancelling Appointment, Please Wait...", @@ -1747,7 +1747,7 @@ "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time.", "searchFor": "Search For", "m": "m", -"inch": "in", + "inch": "in", "updateEmailAddressTitle": "Enter the new email address to be updated: ", "enterNewNewContactNumber": "Enter the new emergency contact number to be updated: ", "nationalIdFileNumber": "National ID/File Number", @@ -1765,10 +1765,10 @@ "selectCriteria": "Select Criteria", "locationServicesDisabled": "Weather is unavailable because location services are disabled. Please enable location services to see the weather information.", "selectDateTimeKey": "Select", - "carbsAlert":"Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.", + "carbsAlert": "Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.", "bodyFatAlert": "Estimate the total body fat based on the size.", "ibwAlert": "Calculates the ideal body weight based on height, weight and body size.", - "glucoseAlert":"Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)", + "glucoseAlert": "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)", "selectBloodGroup": "Select Blood Group", "routePrescription": "Route", "noNearestER": "No nearest Er Around you", @@ -1810,9 +1810,9 @@ "dragToMoveAround": "Drag to move around", "startExploring": "Start exploring", "symptomsCheckSuggestions": "Suggestions", -"appointmentArrived": "Arrived", + "appointmentArrived": "Arrived", "calendarPermissionAlertForAppointment": "Please grant calendar access permission from app settings to set appointment reminder.", - "rechargePageKey": "Recharge Wallet", + "rechargePageKey": "Recharge Wallet", "selectFamilyFile": "Select", "ancillaryOrdersListNew": "Ancillary Orders List", "medicalReportNew": "Medical Report", @@ -1835,7 +1835,8 @@ "maxOneFileAllowed": "Only 1 file can be uploaded", "fileSizeExceedsLimit": "File size must not exceed 1 MB", "completedPrescriptionOrder": "Completed", - "submitEReferral": "Submit" + "submitEReferral": "Submit", + "requestRefund": "Request Refund" } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b796a719..601f8fb0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -601,7 +601,7 @@ SPEC CHECKSUMS: OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba - PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82 + PayFortSDK: e12f79051ae15b2e29536771a45354900180bfcb permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 @@ -612,7 +612,7 @@ SPEC CHECKSUMS: shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 + SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 40cea44b..37dd6989 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* 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 */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; }; @@ -25,7 +26,6 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -52,11 +52,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 05E1B2A0E6844572ADA81174 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3EAE1F31D8D0AA94CA6C11EB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = ""; }; @@ -70,11 +72,10 @@ 687F8A672F828A6300B0FFC1 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ring_30Sec.caf; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7BEF44ABFEF585D64D3F266C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -82,8 +83,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,10 +91,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, 687F8A682F828A6300B0FFC1 /* PenguinINRenderer.xcframework in Frameworks */, 687F8A692F828A6300B0FFC1 /* Penguin.xcframework in Frameworks */, 687F8A6A2F828A6300B0FFC1 /* PenNavUI.xcframework in Frameworks */, + 1587365701FF0D2AAAFED0D8 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -135,7 +135,7 @@ 687F8A652F828A6300B0FFC1 /* Penguin.xcframework */, 687F8A662F828A6300B0FFC1 /* PenguinINRenderer.xcframework */, 687F8A672F828A6300B0FFC1 /* PenNavUI.xcframework */, - D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, + A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -143,9 +143,9 @@ 79DD2093A1D9674C94359FC8 /* Pods */ = { isa = PBXGroup; children = ( - 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */, - 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */, - D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */, + 3EAE1F31D8D0AA94CA6C11EB /* Pods-Runner.debug.xcconfig */, + 05E1B2A0E6844572ADA81174 /* Pods-Runner.release.xcconfig */, + 7BEF44ABFEF585D64D3F266C /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -227,15 +227,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */, + EA10FC42B68E41E75A052FAA /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, - 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5D78F615DF2D966539B34DE2 /* [CP] Embed Pods Frameworks */, + 4AA9AE39AD56A64A1F16E548 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -325,7 +325,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */ = { + 4AA9AE39AD56A64A1F16E548 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -342,7 +342,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */ = { + 5D78F615DF2D966539B34DE2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -374,7 +374,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */ = { + EA10FC42B68E41E75A052FAA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 74fa0ee7..15541878 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -269,6 +269,9 @@ class ApiConsts { // static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; // static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + //Refund Request + static String getRefundableInvoices = "Services/Outps.svc/REST/getRefundableInvoices"; + static String getAiAnalysisURL = "https://api.labpredict.com"; static String getAiRadiologyAnalysis = "$getAiAnalysisURL/radiology"; static String getAiOverViewLabOrdersNew = "$getAiAnalysisURL/analyze_lab_orders"; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 1de302b7..1e65535a 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_repo.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; @@ -12,8 +13,6 @@ import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo. import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; -import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; -import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; @@ -23,6 +22,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; @@ -55,6 +55,8 @@ import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_v import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/refund_request/refund_request_repo.dart'; +import 'package:hmg_patient_app_new/features/refund_request/refund_request_view_model.dart'; import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; @@ -66,7 +68,6 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.da import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; -import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_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'; @@ -83,13 +84,11 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../features/monthly_reports/monthly_reports_repo.dart'; -import '../features/qr_parking/qr_parking_view_model.dart'; -import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; - import '../features/active_prescriptions/active_prescriptions_repo.dart'; import '../features/monthly_reports/terms_conditions_repo.dart'; import '../features/monthly_reports/terms_conditions_view_model.dart'; +import '../features/qr_parking/qr_parking_view_model.dart'; +import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; GetIt getIt = GetIt.instance; @@ -176,18 +175,14 @@ class AppDependencies { getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerFactory( - () => TermsConditionsViewModel( - termsConditionsRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ProfileSettingsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => RefundRequestRepoImp(apiClient: getIt(), loggerService: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -198,13 +193,16 @@ class AppDependencies { () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), ); - getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton( + () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton( + () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); - getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton( + () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel(payfortRepo: getIt(), errorHandlerService: getIt()), @@ -223,7 +221,12 @@ class AppDependencies { getIt.registerLazySingleton( () => BookAppointmentsViewModel( - bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()), + bookAppointmentsRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt(), + myAppointmentsViewModel: getIt(), + locationUtils: getIt(), + dialogService: getIt()), ); getIt.registerLazySingleton( @@ -237,7 +240,13 @@ class AppDependencies { getIt.registerLazySingleton( () => AuthenticationViewModel( - authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), + authenticationRepo: getIt(), + cacheService: getIt(), + navigationService: getIt(), + dialogService: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + localAuthService: getIt()), ); getIt.registerLazySingleton(() => ProfileSettingsViewModel( @@ -310,22 +319,33 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt())); + getIt.registerLazySingleton( + () => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt())); //commenting this because its already define there was on run time error because of this. - getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton( + () => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt())); - getIt.registerFactory(() => QrParkingViewModel(qrParkingRepo: getIt(), errorHandlerService: getIt(), cacheService: getIt())); + getIt.registerLazySingleton( + () => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt())); + getIt.registerFactory(() => QrParkingViewModel( + qrParkingRepo: getIt(), errorHandlerService: getIt(), cacheService: getIt())); getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); + getIt.registerLazySingleton( + () => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton( + () => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); + getIt.registerLazySingleton(() => RefundRequestViewModel( + appState: getIt(), + refundRequestRepo: getIt(), + errorHandlerService: getIt(), + )); } } diff --git a/lib/features/refund_request/models/req_models/submit_refund_request_model.dart b/lib/features/refund_request/models/req_models/submit_refund_request_model.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/features/refund_request/models/req_models/submit_refund_request_model.dart @@ -0,0 +1 @@ + diff --git a/lib/features/refund_request/models/resp_models/refund_request_list_response_model.dart b/lib/features/refund_request/models/resp_models/refund_request_list_response_model.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/features/refund_request/models/resp_models/refund_request_list_response_model.dart @@ -0,0 +1 @@ + diff --git a/lib/features/refund_request/models/resp_models/refundable_invoices_response_model.dart b/lib/features/refund_request/models/resp_models/refundable_invoices_response_model.dart new file mode 100644 index 00000000..1df3e76b --- /dev/null +++ b/lib/features/refund_request/models/resp_models/refundable_invoices_response_model.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; + +class RefundableInvoicesResponseModel { + final String? errorCode; + final String? message; + final List? refundableInvoiceItems; + final int? statusCode; + + RefundableInvoicesResponseModel({ + this.errorCode, + this.message, + this.refundableInvoiceItems, + this.statusCode, + }); + + factory RefundableInvoicesResponseModel.fromJson(Map json) { + return RefundableInvoicesResponseModel( + errorCode: json['ErrorCode'] as String?, + message: json['Message'] as String?, + refundableInvoiceItems: json['RefundableInvoiceItems'] != null + ? (json['RefundableInvoiceItems'] as List) + .map((item) => RefundableInvoiceItem.fromJson(item as Map)) + .toList() + : null, + statusCode: json['StatusCode'] as int?, + ); + } +} + +class RefundableInvoiceItem { + final int? appointmentNo; + final int? clinicID; + final String? clinicName; + final int? doctorID; + final String? doctorName; + final String? invoiceDate; + final int? invoiceNo; + final String? nationalID; + final int? patientID; + final int? projectID; + + RefundableInvoiceItem({ + this.appointmentNo, + this.clinicID, + this.clinicName, + this.doctorID, + this.doctorName, + this.invoiceDate, + this.invoiceNo, + this.nationalID, + this.patientID, + this.projectID, + }); + + factory RefundableInvoiceItem.fromRawJson(String str) => + RefundableInvoiceItem.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RefundableInvoiceItem.fromJson(Map json) { + return RefundableInvoiceItem( + appointmentNo: json['AppointmentNo'] as int?, + clinicID: json['ClinicID'] as int?, + clinicName: json['ClinicName'] as String?, + doctorID: json['DoctorID'] as int?, + doctorName: json['DoctorName'] as String?, + invoiceDate: json['InvoiceDate'] as String?, + invoiceNo: json['InvoiceNo'] as int?, + nationalID: json['NationalID'] as String?, + patientID: json['PatientID'] as int?, + projectID: json['ProjectID'] as int?, + ); + } + + Map toJson() { + return { + 'AppointmentNo': appointmentNo, + 'ClinicID': clinicID, + 'ClinicName': clinicName, + 'DoctorID': doctorID, + 'DoctorName': doctorName, + 'InvoiceDate': invoiceDate, + 'InvoiceNo': invoiceNo, + 'NationalID': nationalID, + 'PatientID': patientID, + 'ProjectID': projectID, + }; + } +} + diff --git a/lib/features/refund_request/refund_request_repo.dart b/lib/features/refund_request/refund_request_repo.dart new file mode 100644 index 00000000..e9f64d69 --- /dev/null +++ b/lib/features/refund_request/refund_request_repo.dart @@ -0,0 +1,81 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/refund_request/models/resp_models/refundable_invoices_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class RefundRequestRepo { + Future>>> + getRefundableInvoices({required int projectID}); +} + +class RefundRequestRepoImp implements RefundRequestRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + RefundRequestRepoImp({required this.apiClient, required this.loggerService}); + + @override + Future>>> + getRefundableInvoices({required int projectID}) async { + // ApiClient auto-injects: PatientID, TokenID, LanguageID, PatientOutSA, + // PatientTypeID, IPAdress, generalid, VersionID, Channel + Map body = { + "ProjectID": projectID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getRefundableInvoices, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final refundableInvoicesObject = + response['RefundableInvoicesObject']; + + if (refundableInvoicesObject != null) { + final invoiceItemsJson = + refundableInvoicesObject['RefundableInvoiceItems']; + + final List invoicesList = + invoiceItemsJson != null + ? (invoiceItemsJson as List) + .map((item) => RefundableInvoiceItem.fromJson( + item as Map)) + .toList() + : []; + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: invoicesList, + ); + } else { + failure = DataParsingFailure("RefundableInvoicesObject is null"); + } + } catch (e) { + loggerService.logError("getRefundableInvoices parsing error: $e"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("getRefundableInvoices exception: $e"); + return Left(UnknownFailure(e.toString())); + } + } +} + diff --git a/lib/features/refund_request/refund_request_view_model.dart b/lib/features/refund_request/refund_request_view_model.dart new file mode 100644 index 00000000..030b2a70 --- /dev/null +++ b/lib/features/refund_request/refund_request_view_model.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/refund_request/models/resp_models/refundable_invoices_response_model.dart'; +import 'package:hmg_patient_app_new/features/refund_request/refund_request_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +enum RefundTabType { invoices, hmgWallet } + +/// Lightweight model representing a refundable procedure line item. +class RefundableProcedure { + final int procedureId; + final String procedureName; + final String? procedureNameN; + final double patientShare; + bool isSelected; + + RefundableProcedure({ + required this.procedureId, + required this.procedureName, + this.procedureNameN, + required this.patientShare, + this.isSelected = false, + }); +} + +/// Lightweight model for an invoice dropdown item. +class RefundInvoiceItem { + final int invoiceNo; + final int appointmentNo; + final int projectId; + final String displayLabel; + + const RefundInvoiceItem({ + required this.invoiceNo, + required this.appointmentNo, + required this.projectId, + required this.displayLabel, + }); +} + +class RefundRequestViewModel extends ChangeNotifier { + final AppState appState; + final RefundRequestRepo refundRequestRepo; + final ErrorHandlerService errorHandlerService; + + RefundRequestViewModel({ + required this.appState, + required this.refundRequestRepo, + required this.errorHandlerService, + }); + + // ── Tab ────────────────────────────────────────────────────────────────── + RefundTabType selectedTab = RefundTabType.invoices; + + void setTab(RefundTabType tab) { + selectedTab = tab; + notifyListeners(); + } + + // ── Step (0 = select invoice+hospital, 1 = select procedures) ──────────── + int currentStep = 0; + + void goToStep(int step) { + currentStep = step; + notifyListeners(); + } + + void nextStep() { + if (currentStep < 1) { + currentStep++; + notifyListeners(); + } + } + + void previousStep() { + if (currentStep > 0) { + currentStep--; + notifyListeners(); + } + } + + // ── Hospital selection ──────────────────────────────────────────────────── + HospitalsModel? selectedHospital; + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + // Reset invoice and procedures whenever hospital changes + selectedInvoice = null; + procedures.clear(); + refundableInvoicesList.clear(); + notifyListeners(); + + // Fetch refundable invoices for the selected hospital's project + if (hospital != null && hospital.iD != null) { + final projectID = hospital.iD is int ? hospital.iD as int : int.tryParse(hospital.iD.toString()); + if (projectID != null) { + getRefundableInvoices(projectID: projectID); + } + } + } + + void clearHospital() { + selectedHospital = null; + selectedInvoice = null; + procedures.clear(); + notifyListeners(); + } + + // ── Invoice selection ───────────────────────────────────────────────────── + RefundInvoiceItem? selectedInvoice; + List refundableInvoicesList = []; + bool isRefundableInvoicesLoading = false; + + Future getRefundableInvoices({ + required int projectID, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isRefundableInvoicesLoading = true; + refundableInvoicesList.clear(); + notifyListeners(); + + final result = await refundRequestRepo.getRefundableInvoices(projectID: projectID); + + result.fold( + (failure) { + isRefundableInvoicesLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (apiResponse) { + isRefundableInvoicesLoading = false; + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + refundableInvoicesList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) onSuccess(apiResponse); + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Failed to load invoices"); + } + } + }, + ); + } + + void setSelectedInvoice(RefundInvoiceItem invoice) { + selectedInvoice = invoice; + procedures.clear(); + notifyListeners(); + } + + void clearInvoice() { + selectedInvoice = null; + procedures.clear(); + notifyListeners(); + } + + // ── Procedures ──────────────────────────────────────────────────────────── + List procedures = []; + bool isProceduresLoading = false; + + void setProcedures(List list) { + procedures = list; + isProceduresLoading = false; + notifyListeners(); + } + + void toggleProcedureSelection(RefundableProcedure procedure) { + procedure.isSelected = !procedure.isSelected; + notifyListeners(); + } + + List get selectedProcedures => procedures.where((p) => p.isSelected).toList(); + + // ── Calculation ─────────────────────────────────────────────────────────── + double get totalRefundableAmount { + double total = 0.0; + for (final p in selectedProcedures) { + total += p.patientShare; + } + return total; + } + + // ── Validation ──────────────────────────────────────────────────────────── + bool get isStep1Valid => selectedHospital != null && selectedInvoice != null; + + bool get isStep2Valid => selectedProcedures.isNotEmpty; +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index ae8c6817..84cff5ad 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -2,7 +2,7 @@ // ignore_for_file: constant_identifier_names -abstract class LocaleKeys { +abstract class LocaleKeys { static const english = 'english'; static const arabic = 'arabic'; static const login = 'login'; @@ -1838,5 +1838,5 @@ abstract class LocaleKeys { static const fileSizeExceedsLimit = 'fileSizeExceedsLimit'; static const completedPrescriptionOrder = 'completedPrescriptionOrder'; static const submitEReferral = 'submitEReferral'; - + static const requestRefund = 'requestRefund'; } diff --git a/lib/main.dart b/lib/main.dart index ffe2043e..938904a3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,17 +10,16 @@ import 'package:flutter/services.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/features/weather/weather_view_model.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; -import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; @@ -39,22 +38,19 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/refund_request/refund_request_view_model.dart'; import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; -import 'package:hmg_patient_app_new/features/translations/custom_asset_loader.dart'; -import 'package:hmg_patient_app_new/features/translations/translation_service.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; -import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.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/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; -import 'package:hmg_patient_app_new/theme/colors.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' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; @@ -72,7 +68,6 @@ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs - class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { @@ -257,11 +252,15 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), - ) , + ), ChangeNotifierProvider( create: (_) => getIt.get(), - ), ChangeNotifierProvider( + ), + ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index b7feaadc..2810b221 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -11,8 +11,8 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/wallet_payment_confirm_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; @@ -44,7 +44,9 @@ class _RechargeWalletPageState extends State { // Clear errors when entering recharge wallet page WidgetsBinding.instance.addPostFrameCallback((_) { habibWalletVM = context.read(); - habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + habibWalletVM.setDepositorDetails( + appState.getAuthenticatedUser()!.patientId.toString(), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", appState.getAuthenticatedUser()!.mobileNumber!); habibWalletVM.setSelectedRechargeType(0); habibWalletVM.getProjectsList(); @@ -97,262 +99,272 @@ class _RechargeWalletPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( - builder: (context, viewModel, child) { - return Container( - height: 135.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - side: BorderSide( - color: viewModel.amountError != null ? AppColors.primaryRedBorderColor : AppColors.textColor, - width: 2.h, - ), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - //TODO: Check with hussain to show AED or SAR - LocaleKeys.amount.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), - Spacer(), - Row( - crossAxisAlignment: CrossAxisAlignment.end, + Consumer( + builder: (context, viewModel, child) { + return Container( + height: 135.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + side: BorderSide( + color: viewModel.amountError != null ? AppColors.primaryRedBorderColor : AppColors.textColor, + width: 2.h, + ), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: 150.h, - child: TextInputWidget( - controller: amountTextController, - labelText: "", - hintText: "", - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - fontSize: 25.f, - padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), - focusNode: _amountFocusNode, - isWalletAmountInput: true, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - fontFamily: "Poppins", - onChange: (value) { - // Clear error when user starts typing - viewModel.clearAmountError(); - }, - // leadingIcon: AppAssets.student_card, - ), + //TODO: Check with hussain to show AED or SAR + LocaleKeys.amount.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), + Spacer(), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox( + width: 150.h, + child: TextInputWidget( + controller: amountTextController, + labelText: "", + hintText: "", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + fontSize: 25.f, + padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), + focusNode: _amountFocusNode, + isWalletAmountInput: true, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + fontFamily: "Poppins", + onChange: (value) { + // Clear error when user starts typing + viewModel.clearAmountError(); + }, + // leadingIcon: AppAssets.student_card, + ), + ), + const Spacer(), + LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, isBold: true), + ], ), - const Spacer(), - LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, isBold: true), ], ), - ], - ), - ), - ); - }, - ), - // Show error message if exists - Consumer( - builder: (context, viewModel, child) { - if (viewModel.amountError != null) { - return Padding( - padding: EdgeInsets.only(left: 16.w, top: 8.h), - child: Text( - viewModel.amountError!, - style: TextStyle( - color: AppColors.primaryRedColor, - fontSize: 12.f, ), - ), - ); - } - return SizedBox.shrink(); - }, - ), - SizedBox(height: 24.h), - Consumer(builder: (context, habibWalletVM, child) { - return Container( - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: BorderRadius.circular(24.h), - border: Border.all( - color: habibWalletVM.hospitalError != null ? AppColors.primaryRedBorderColor : Colors.transparent, - width: 2.h, - ), + ); + }, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // Show error message if exists + Consumer( + builder: (context, viewModel, child) { + if (viewModel.amountError != null) { + return Padding( + padding: EdgeInsets.only(left: 16.w, top: 8.h), + child: Text( + viewModel.amountError!, + style: TextStyle( + color: AppColors.primaryRedColor, + fontSize: 12.f, + ), + ), + ); + } + return SizedBox.shrink(); + }, + ), + SizedBox(height: 24.h), + Consumer(builder: (context, habibWalletVM, child) { + return Container( + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(24.h), + border: Border.all( + color: habibWalletVM.hospitalError != null ? AppColors.primaryRedBorderColor : Colors.transparent, + width: 2.h, + ), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h, applyThemeColor: false), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + Row( children: [ - (habibWalletVM.getSelectedRechargeTypeValue()).toText12(color: AppColors.greyTextColor, isBold: true), - "${LocaleKeys.medicalFile.tr(context: context)}: ${habibWalletVM.fileNumber}" - .toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), + Utils.buildSvgWithAssets( + icon: AppAssets.my_account_icon, width: 40.h, height: 40.h, applyThemeColor: false), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (habibWalletVM.getSelectedRechargeTypeValue()).toText12(color: AppColors.greyTextColor, isBold: true), + "${LocaleKeys.medicalFile.tr(context: context)}: ${habibWalletVM.fileNumber}" + .toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), + ], + ), ], ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), - ], - ).onPress(() async { - habibWalletVM.setCurrentIndex(0); - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.medicalFile.tr(context: context), - titleWidget: Consumer(builder: (context, habibWalletVM, child) { - return habibWalletVM.currentIndex != 0 - ? IconButton( - icon: Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 24.h, height: 24.h)), - padding: EdgeInsets.zero, - onPressed: () => habibWalletVM.setCurrentIndex(0), - highlightColor: Colors.transparent, - ) - : LocaleKeys.medicalFile.tr(context: context).toText20(isBold: true); - }), child: Consumer(builder: (context, habibWalletVM, child) { - return MultiPageBottomSheet(); - }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + ).onPress(() async { + habibWalletVM.setCurrentIndex(0); + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.medicalFile.tr(context: context), + titleWidget: Consumer(builder: (context, habibWalletVM, child) { + return habibWalletVM.currentIndex != 0 + ? IconButton( + icon: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 24.h, height: 24.h)), + padding: EdgeInsets.zero, + onPressed: () => habibWalletVM.setCurrentIndex(0), + highlightColor: Colors.transparent, + ) + : LocaleKeys.medicalFile.tr(context: context).toText20(isBold: true); + }), child: Consumer(builder: (context, habibWalletVM, child) { + return MultiPageBottomSheet(); + }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.select_hospital_icon, width: 40.h, height: 40.h, applyThemeColor: false), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + Row( children: [ - LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), - SizedBox( - width: MediaQuery.of(context).size.width * 0.55, - child: (habibWalletVM.selectedHospital != null ? habibWalletVM.selectedHospital!.name : LocaleKeys.selectHospital.tr(context: context))! - .toText14(color: AppColors.textColor, isBold: true), + Utils.buildSvgWithAssets( + icon: AppAssets.select_hospital_icon, width: 40.h, height: 40.h, applyThemeColor: false), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), + SizedBox( + width: MediaQuery.of(context).size.width * 0.55, + child: (habibWalletVM.selectedHospital != null + ? habibWalletVM.selectedHospital!.name + : LocaleKeys.selectHospital.tr(context: context))! + .toText14(color: AppColors.textColor, isBold: true), + ), + ], ), ], ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), - ], - ).onPress(() { - showCommonBottomSheetWithoutHeight(context, - title: LocaleKeys.selectHospital.tr(context: context), isDismissible: false, child: SelectHospitalBottomSheet(), callBackFunc: () {}); - }), - // Show hospital error message if exists - if (habibWalletVM.hospitalError != null) - Padding( - padding: EdgeInsets.only(top: 8.h), - child: Text( - habibWalletVM.hospitalError!, - style: TextStyle( - color: AppColors.primaryRedColor, - fontSize: 12.f, + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.selectHospital.tr(context: context), + isDismissible: false, + child: SelectHospitalBottomSheet(), + callBackFunc: () {}); + }), + // Show hospital error message if exists + if (habibWalletVM.hospitalError != null) + Padding( + padding: EdgeInsets.only(top: 8.h), + child: Text( + habibWalletVM.hospitalError!, + style: TextStyle( + color: AppColors.primaryRedColor, + fontSize: 12.f, + ), + ), ), - ), - ), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h, applyThemeColor: false), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( children: [ - LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), - "${appState.getAuthenticatedUser()!.emailAddress}".toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), + Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h, applyThemeColor: false), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), + "${appState.getAuthenticatedUser()!.emailAddress}" + .toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), + ], + ), ], ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.notes.tr(context: context), + hintText: "", + controller: notesTextController, + keyboardType: TextInputType.text, + isEnable: true, + prefix: null, + autoFocus: false, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + leadingIcon: AppAssets.notes_icon, + hasError: false, + ), + SizedBox(height: 8.h), ], ), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.notes.tr(context: context), - hintText: "", - controller: notesTextController, - keyboardType: TextInputType.text, - isEnable: true, - prefix: null, - autoFocus: false, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - leadingIcon: AppAssets.notes_icon, - hasError: false, - ), - SizedBox(height: 8.h), - ], - ), - ), - ); - }), - ], + ), + ); + }), + ], + ), + ), ), ), ), - ), - ), - //TODO: Handle Family member selection & Other Account Selection - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: CustomButton( - text: LocaleKeys.next.tr(context: context), - onPressed: () { - _amountFocusNode.unfocus(); - FocusScope.of(context).unfocus(); + //TODO: Handle Family member selection & Other Account Selection + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: CustomButton( + text: LocaleKeys.next.tr(context: context), + onPressed: () { + _amountFocusNode.unfocus(); + FocusScope.of(context).unfocus(); - // Use ViewModel validation method - if (habibWalletVM.validateRechargeForm(amountTextController.text)) { - habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text.replaceAll(',', ''))); - habibWalletVM.setNotesText(notesTextController.text); - // habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", - // appState.getAuthenticatedUser()!.mobileNumber!); - Navigator.of(context).push( - CustomPageRoute( - page: WalletPaymentConfirmPage(), - ), - ); - } - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w600, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.appointment_pay_icon, - iconColor: Colors.white, - iconSize: 18.h, - ).paddingSymmetrical(24.h, 24.h), + // Use ViewModel validation method + if (habibWalletVM.validateRechargeForm(amountTextController.text)) { + habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text.replaceAll(',', ''))); + habibWalletVM.setNotesText(notesTextController.text); + // habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // appState.getAuthenticatedUser()!.mobileNumber!); + Navigator.of(context).push( + CustomPageRoute( + page: WalletPaymentConfirmPage(), + ), + ); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.appointment_pay_icon, + iconColor: Colors.white, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], ), - ], - ), - ), + ), ), ); } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index b1495fed..29aa9dc7 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/enums.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/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; import 'dart:ui' as ui; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ // CustomButton( // text: LocaleKeys.byClinic.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( richText: Row( children: [ "${group.testDetails!.length} ".toText10(color: AppColors.successColor, isEnglishOnly: true, weight: FontWeight.w600), LocaleKeys.tests.tr(context: context).toText10(color: AppColors.successColor, weight: FontWeight.w600), ], ), labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (group.doctorName ?? group.doctorNameEnglish ?? "").toString().toText14(isBold: true)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}".toText14(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), order.testDetails![index].testDescriptionEn!.toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Image.network( // order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", // width: 24.w, // height: 24.h, // fit: BoxFit.cover, // ).circle(100), // SizedBox(width: 8.h), // Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(isBold: true)), // ], // ), // SizedBox(height: 8.h), // Wrap( // direction: Axis.horizontal, // spacing: 4.h, // runSpacing: 4.h, // children: [ // AppCustomChipWidget( // labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), isEnglishOnly: true, // ), // Directionality( // textDirection: ui.TextDirection.ltr, // child: AppCustomChipWidget( // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // isEnglishOnly: true, // )), // AppCustomChipWidget( // labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), // ), // ], // ), // // Row( // // children: [ // // CustomButton( // // text: ("Order No: ".needTranslation + order.orderNo!), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // isBold: true, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // SizedBox(width: 8.h), // // CustomButton( // // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // isBold: true, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // ], // // ), // // SizedBox(height: 8.h), // // Row( // // children: [ // // CustomButton( // // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // isBold: true, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // ], // // ), // SizedBox(height: 12.h), // Row( // children: [ // Expanded(flex: 2, child: SizedBox()), // // Expanded( // // flex: 1, // // child: Container( // // height: 40.h, // // width: 40.w, // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // // color: AppColors.textColor, // // borderRadius: 12, // // ), // // child: Padding( // // padding: EdgeInsets.all(12.h), // // child: Transform.flip( // // flipX: _appState.isArabic(), // // child: Utils.buildSvgWithAssets( // // icon: AppAssets.forward_arrow_icon_small, // // iconColor: AppColors.whiteColor, // // fit: BoxFit.contain, // // ), // // ), // // ), // // ).onPress(() { // // model.currentlySelectedPatientOrder = order; // // labProvider.getPatientLabResultByHospital(order); // // labProvider.getPatientSpecialResult(order); // // Navigator.of(context).push( // // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // // ); // // }), // // ) // // Expanded( // flex: 2, // child: CustomButton( // icon: AppAssets.view_report_icon, // iconColor: AppColors.primaryRedColor, // iconSize: 16.h, // text: LocaleKeys.viewResults.tr(context: context), // onPressed: () { // labViewModel.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }, // backgroundColor: AppColors.secondaryLightRedColor, // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 14, // isBold: true, // borderRadius: 12, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // ) // ], // ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/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'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.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'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ // CustomButton( // text: LocaleKeys.byClinic.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder() .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( richText: Row( children: [ "${group.testDetails!.length} ".toText10( color: AppColors.successColor, isEnglishOnly: true, weight: FontWeight.w600), LocaleKeys.tests .tr(context: context) .toText10(color: AppColors.successColor, weight: FontWeight.w600), ], ), labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded( child: (group.doctorName ?? group.doctorNameEnglish ?? "") .toString() .toText14(isBold: true)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate( DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}" .toText14(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), order.testDetails![index].testDescriptionEn!.toText12( isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider( color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index b60cd8bc..560768b5 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -40,9 +40,6 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; -import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; -import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; -import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_approvals_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; @@ -62,6 +59,7 @@ import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.da import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; +import 'package:hmg_patient_app_new/presentation/refund_request/refund_request_create_page.dart'; import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -584,7 +582,6 @@ class _MedicalFilePageState extends State { ) : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.bgRedLightColor, borderRadius: 12.r, hasShadow: false, ), @@ -653,11 +650,7 @@ class _MedicalFilePageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: InsuranceApprovalsPage(), - ), - ); + Navigator.of(context).push(CustomPageRoute(page: InsuranceApprovalsPage())); }), MedicalFileCard( label: LocaleKeys.myInvoicesList.tr(context: context), @@ -666,10 +659,20 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.invoices_list_icon, isLargeText: true, iconSize: 36.w, + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MyInvoicesList())); + }), + MedicalFileCard( + label: LocaleKeys.requestRefund.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ancillary_orders_list_icon, + isLargeText: true, + iconSize: 36.w, ).onPress(() { Navigator.of(context).push( CustomPageRoute( - page: MyInvoicesList(), + page: const RefundRequestCreatePage(initialTabIndex: 0), ), ); }), diff --git a/lib/presentation/refund_request/refund_request_create_page.dart b/lib/presentation/refund_request/refund_request_create_page.dart new file mode 100644 index 00000000..692a10a6 --- /dev/null +++ b/lib/presentation/refund_request/refund_request_create_page.dart @@ -0,0 +1,442 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/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/refund_request/refund_request_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/refund_request/widgets/select_hospital_bottom_sheet_refund.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'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/stepper/stepper_widget.dart'; +import 'package:provider/provider.dart'; + +class RefundRequestCreatePage extends StatefulWidget { + /// 0 = Invoices (default), 1 = HMG Wallet + final int initialTabIndex; + + const RefundRequestCreatePage({super.key, this.initialTabIndex = 0}); + + @override + State createState() => _RefundRequestCreatePageState(); +} + +class _RefundRequestCreatePageState extends State { + late RefundRequestViewModel _vm; + late HabibWalletViewModel _habibWalletVM; + late AppState _appState; + + // TODO: Replace with actual procedure list fetched from API per invoice + List _mockProceduresForInvoice(int invoiceNo) => [ + RefundableProcedure(procedureId: 101, procedureName: 'Consultation', procedureNameN: 'استشارة', patientShare: 120.0), + RefundableProcedure(procedureId: 102, procedureName: 'Blood Test – CBC', procedureNameN: 'تحليل دم شامل', patientShare: 85.50), + RefundableProcedure(procedureId: 103, procedureName: 'X-Ray Chest', procedureNameN: 'أشعة سينية صدر', patientShare: 200.0), + RefundableProcedure(procedureId: 104, procedureName: 'ECG', procedureNameN: 'تخطيط قلب', patientShare: 150.0), + ]; + + @override + void initState() { + super.initState(); + _appState = getIt.get(); + WidgetsBinding.instance.addPostFrameCallback((_) { + // Load hospital list the same way RechargeWalletPage does + _habibWalletVM.getProjectsList(); + }); + } + + // ── Stepper ─────────────────────────────────────────────────────────────── + + Widget _buildStepper(RefundRequestViewModel vm) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: LayoutBuilder( + builder: (context, constraints) { + // StepperWidget appends SizedBox(width: 8.h) after itself + final itemWidth = (constraints.maxWidth - 2 * 8.h) / 2; + return Row( + children: List.generate(2, (index) { + final isActive = index <= vm.currentStep; + final isThumb = index == vm.currentStep; + return StepperWidget( + itemWidth, + isActive ? AppColors.primaryRedColor : AppColors.greyColor, + isThumb, + 4.h, + ); + }), + ); + }, + ), + ); + } + + // ── Step 1 — exactly mirrors RechargeWalletPage hospital row ───────────── + + Widget _buildStep1Content(RefundRequestViewModel vm) { + final isArabic = _appState.isArabic(); + return Consumer( + builder: (context, refundVM, _) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.select_hospital_icon, + width: 40.h, + height: 40.h, + applyThemeColor: false, + ), + SizedBox(width: 8.h), + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), + (refundVM.selectedHospital != null + ? ((isArabic + ? (refundVM.selectedHospital!.nameN?.toString() ?? refundVM.selectedHospital!.name) + : refundVM.selectedHospital!.name) ?? + LocaleKeys.selectHospital.tr(context: context)) + : LocaleKeys.selectHospital.tr(context: context)) + .toText14(color: AppColors.textColor, isBold: true), + ]), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.arrow_down, + width: 25.h, + height: 25.h, + ), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.selectHospital.tr(context: context), + isDismissible: false, + child: SelectHospitalBottomSheetRefund(), + callBackFunc: () {}, + ); + }), + ), + ); + }, + ); + } + + // ── Step 2 — procedure checkboxes ───────────────────────────────────────── + + Widget _buildProcedureCard(RefundableProcedure procedure, int index) { + final isArabic = _appState.isArabic(); + final name = isArabic && procedure.procedureNameN != null ? procedure.procedureNameN! : procedure.procedureName; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 400), + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: procedure.isSelected ? AppColors.primaryRedColor.withAlpha(12) : AppColors.whiteColor, + borderRadius: 16.h, + hasShadow: true, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _vm.toggleProcedureSelection(procedure), + borderRadius: BorderRadius.circular(16.h), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + value: procedure.isSelected, + onChanged: (_) => _vm.toggleProcedureSelection(procedure), + activeColor: AppColors.primaryRedColor, + side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1.5), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + ), + SizedBox(width: 8.w), + Expanded(child: name.toText14(isBold: true)), + SizedBox(width: 8.w), + Utils.getPaymentAmountWithSymbol( + procedure.patientShare.toStringAsFixed(2).toText14( + isBold: true, + isEnglishOnly: true, + color: AppColors.primaryRedColor, + ), + AppColors.textColorLight, + 13, + isSaudiCurrency: true, + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildStep2Content(RefundRequestViewModel vm) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Text( + 'Select Procedures', + style: TextStyle( + fontSize: 20.f, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + letterSpacing: -0.5, + ), + ), + ), + SizedBox(height: 24.h), + if (vm.isProceduresLoading) + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 24.w), + itemCount: 4, + itemBuilder: (_, index) => Container( + height: 56.h, + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.h, + ), + ).toShimmer2(isShow: true, radius: 16.r), + ) + else if (vm.procedures.isEmpty) + Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 24.h), + child: 'No procedures available for refund'.toText14(color: AppColors.greyTextColor), + ), + ) + else + AnimationLimiter( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: 24.h, + hasShadow: false, + ), + padding: EdgeInsets.all(12.w), + child: Column( + children: List.generate( + vm.procedures.length, + (index) => _buildProcedureCard(vm.procedures[index], index), + ), + ), + ), + ), + ), + ], + ); + } + + // ── Bottom bar ──────────────────────────────────────────────────────────── + + Widget _buildBottomBar(RefundRequestViewModel vm) { + final isStep2 = vm.currentStep == 1; + final isWallet = vm.selectedTab == RefundTabType.hmgWallet; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isStep2 && !isWallet && vm.selectedProcedures.isNotEmpty) ...[ + Padding( + padding: EdgeInsets.fromLTRB(24.w, 16.h, 24.w, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'Total Refundable Amount'.toText12( + color: AppColors.textColorLight, + isBold: true, + ), + Utils.getPaymentAmountWithSymbol( + vm.totalRefundableAmount.toStringAsFixed(2).toText18( + isBold: true, + isEnglishOnly: true, + color: AppColors.primaryRedColor, + ), + AppColors.textColorLight, + 14, + isSaudiCurrency: true, + ), + ], + ), + ), + ], + CustomButton( + text: isStep2 ? LocaleKeys.submit.tr(context: context) : LocaleKeys.next.tr(context: context), + onPressed: () { + if (!isStep2) { + if (isWallet || vm.isStep1Valid) vm.nextStep(); + } else { + // TODO: Submit API call + } + }, + isDisabled: (!isWallet && !isStep2 && !vm.isStep1Valid) || (isStep2 && !isWallet && !vm.isStep2Valid), + ).paddingSymmetrical(24.h, 24.h), + ], + ), + ); + } + + // ── HMG Wallet placeholder ───────────────────────────────────────────────── + + Widget _buildHmgWalletContent() { + return KeyedSubtree( + key: const ValueKey('hmgWallet'), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(24.w), + child: Column( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.wallet, + iconColor: AppColors.primaryRedColor, + width: 48.h, + height: 48.h, + ), + SizedBox(height: 16.h), + 'HMG Wallet Refund'.toText16(isBold: true), + SizedBox(height: 8.h), + 'Refund requests to HMG Wallet will be processed within 1-3 business days.'.toText14(color: AppColors.greyTextColor, isCenter: true), + ], + ), + ), + ), + ); + } + + // ── Build ───────────────────────────────────────────────────────────────── + + @override + Widget build(BuildContext context) { + _habibWalletVM = context.read(); + return Consumer( + builder: (context, vm, _) { + _vm = vm; + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.requestRefund.tr(context: context), + history: () async { + // TODO: Navigate to refund history page + }, + child: SingleChildScrollView( + padding: EdgeInsets.only(bottom: 32.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Tab bar + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: CustomTabBar( + initialIndex: widget.initialTabIndex, + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withAlpha(25), + tabs: [ + CustomTabBarModel(null, 'Invoices'), + CustomTabBarModel(null, 'HMG Wallet'), + ], + onTabChange: (index) { + vm.setTab(index == 0 ? RefundTabType.invoices : RefundTabType.hmgWallet); + vm.goToStep(0); + }, + ), + ), + SizedBox(height: 24.h), + // Stepper (invoices tab only) + if (vm.selectedTab == RefundTabType.invoices) ...[ + _buildStepper(vm), + SizedBox(height: 24.h), + ], + + // Content + AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0.05, 0), + end: Offset.zero, + ).animate(animation), + child: child, + ), + ), + child: vm.selectedTab == RefundTabType.hmgWallet + ? _buildHmgWalletContent() + : vm.currentStep == 0 + ? KeyedSubtree( + key: const ValueKey('step1'), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: _buildStep1Content(vm), + ), + ) + : KeyedSubtree( + key: const ValueKey('step2'), + child: _buildStep2Content(vm), + ), + ), + ], + ), + ), + ), + ), + _buildBottomBar(vm), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/refund_request/widgets/select_hospital_bottom_sheet_refund.dart b/lib/presentation/refund_request/widgets/select_hospital_bottom_sheet_refund.dart new file mode 100644 index 00000000..e38a831e --- /dev/null +++ b/lib/presentation/refund_request/widgets/select_hospital_bottom_sheet_refund.dart @@ -0,0 +1,223 @@ +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/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.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/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/refund_request/refund_request_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/hospital_list_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; + +class SelectHospitalBottomSheetRefund extends StatefulWidget { + const SelectHospitalBottomSheetRefund({super.key}); + + @override + State createState() => + _SelectHospitalBottomSheetRefundState(); +} + +class _SelectHospitalBottomSheetRefundState + extends State { + late RefundRequestViewModel refundVM; + late HabibWalletViewModel habibWalletVM; + bool sortByLocation = false; + bool isLoading = false; + List displayList = []; + + @override + void initState() { + super.initState(); + final appState = getIt.get(); + sortByLocation = (appState.userLat != 0.0) && (appState.userLong != 0.0); + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateDisplayList(); + }); + } + + void _updateDisplayList() { + final appState = getIt.get(); + // Reuse the same hospital list already loaded in HabibWalletViewModel + final hospitals = List.from(habibWalletVM.advancePaymentHospitals); + + if (sortByLocation && appState.userLat != 0.0 && appState.userLong != 0.0) { + hospitals.sort((a, b) { + final distA = (a.latitude != null && a.longitude != null) + ? DoctorMapper.calculateDistance(appState.userLat, appState.userLong, + double.parse(a.latitude!), double.parse(a.longitude!)) + : double.infinity; + final distB = (b.latitude != null && b.longitude != null) + ? DoctorMapper.calculateDistance(appState.userLat, appState.userLong, + double.parse(b.latitude!), double.parse(b.longitude!)) + : double.infinity; + return distA.compareTo(distB); + }); + } + + setState(() { + displayList = hospitals; + isLoading = false; + }); + } + + void _refreshHospitalListAfterApi() { + void listener() { + if (habibWalletVM.advancePaymentHospitals.isNotEmpty) { + habibWalletVM.removeListener(listener); + _updateDisplayList(); + } + } + habibWalletVM.addListener(listener); + habibWalletVM.getProjectsList(); + } + + void _handleSortByLocationToggle(bool value) { + if (value) { + final locationUtils = getIt.get(); + locationUtils.getLocation( + isShowConfirmDialog: true, + onSuccess: (latLng) { + setState(() { + sortByLocation = true; + isLoading = true; + }); + _refreshHospitalListAfterApi(); + }, + onFailure: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.giveLocationPermissionForNearestList + .tr(context: context), + isShowActionButtons: true, + onCancelTap: () => Navigator.of(context).pop(), + onConfirmTap: () async { + Navigator.of(context).pop(); + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + setState(() => sortByLocation = false); + }, + onLocationDeniedForever: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.giveLocationPermissionForNearestList + .tr(context: context), + isShowActionButtons: true, + onCancelTap: () => Navigator.of(context).pop(), + onConfirmTap: () async { + Navigator.of(context).pop(); + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + setState(() => sortByLocation = false); + }, + ); + } else { + final appState = getIt.get(); + appState.resetLocation(); + setState(() { + sortByLocation = false; + isLoading = true; + }); + _refreshHospitalListAfterApi(); + } + } + + @override + Widget build(BuildContext context) { + refundVM = context.read(); + habibWalletVM = context.read(); + + if (displayList.isEmpty && habibWalletVM.advancePaymentHospitals.isNotEmpty) { + displayList = List.from(habibWalletVM.advancePaymentHospitals); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.selectHospitalForAdvancePayment + .tr(context: context) + .toText16(color: AppColors.greyTextColor, isBold: true), + SizedBox(height: 16.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.location, + iconColor: AppColors.greyTextColor, + width: 18.h, + height: 18.h), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), + LocaleKeys.sortByNearestLocation + .tr(context: context) + .toText11(color: AppColors.textColorLight, isBold: true), + ], + ), + ], + ), + Switch( + value: sortByLocation, + onChanged: _handleSortByLocationToggle, + activeThumbColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: 0.15), + ), + ], + ), + ), + SizedBox(height: 8.h), + isLoading + ? SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: Center(child: Utils.getLoadingWidget()), + ) + : SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemCount: displayList.length, + separatorBuilder: (_, __) => SizedBox(height: 16.h), + itemBuilder: (_, index) { + return HospitalListItemAdvancePayment( + hospitalModel: displayList[index], + isLocationEnabled: sortByLocation, + ).onPress(() { + refundVM.setSelectedHospital(displayList[index]); + Navigator.of(context).pop(); + }); + }, + ), + ), + ], + ); + } +} + diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index f4ddff9b..7b34e447 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -41,7 +41,8 @@ import '../core/dependencies.dart'; import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../features/qr_parking/qr_parking_view_model.dart'; -import '../presentation/parking/paking_page.dart'; +import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; +import 'package:hmg_patient_app_new/presentation/refund_request/refund_request_create_page.dart'; import '../services/error_handler_service.dart'; class AppRoutes { @@ -101,6 +102,9 @@ class AppRoutes { // Weather static const String familyFilesPage = '/familyFilesPage'; + // Refund Request + static const String refundRequestCreatePage = '/refundRequestCreatePage'; + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -160,6 +164,10 @@ class AppRoutes { qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), child: const ParkingPage(), - ) + ), + refundRequestCreatePage: (context) { + final args = ModalRoute.of(context)?.settings.arguments as int?; + return RefundRequestCreatePage(initialTabIndex: args ?? 0); + }, }; } diff --git a/pubspec.lock b/pubspec.lock index dd27bab8..ce36792b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1042,13 +1042,13 @@ packages: source: hosted version: "1.2.1" image_picker_android: - dependency: transitive + dependency: "direct main" description: name: image_picker_android - sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+10" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -1082,7 +1082,7 @@ packages: source: hosted version: "0.2.2+1" image_picker_platform_interface: - dependency: transitive + dependency: "direct main" description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"