From c9279e22725d8b87a110ba99e5cbd2b82f6f7990 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 9 Feb 2026 10:40:32 +0300 Subject: [PATCH 1/6] updates --- assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 4 +-- icon.jpg | Bin 0 -> 98097 bytes .../book_appointments_repo.dart | 2 +- .../book_appointments_view_model.dart | 8 +++-- lib/generated/locale_keys.g.dart | 1 + .../review_appointment_page.dart | 2 +- .../book_appointment/select_doctor_page.dart | 2 +- .../widgets/appointment_calendar.dart | 30 +++++++++++------- 9 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 icon.jpg diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 88d5630..3292fb9 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1532,5 +1532,6 @@ "servicePriceList": "قائمة أسعار الخدمات", "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", - "serviceName": "اسم الخدمة" + "serviceName": "اسم الخدمة", + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 47f0d69..5ef9b56 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1524,6 +1524,6 @@ "servicePriceList": "Services Price List", "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", - - "serviceName": "Service Name" + "serviceName": "Service Name", + "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later." } diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65ca5c0525f457452c9f93287908988ceaa1adc0 GIT binary patch literal 98097 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 getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; - final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat formatter = DateFormat('HH:mm', "en_US"); final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f53c5f0..9460775 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1523,5 +1523,6 @@ abstract class LocaleKeys { static const servicePriceListDesc = 'servicePriceListDesc'; static const servicePriceListRights = 'servicePriceListRights'; static const serviceName = 'serviceName'; + static const noFreeSlot = 'noFreeSlot'; } diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index f82295a..5b8ba53 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -120,7 +120,7 @@ class _ReviewAppointmentPageState extends State { ), AppCustomChipWidget( labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}", + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDisplayDate}", ), AppCustomChipWidget( labelText: diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index f8a0ad9..1fc9d9a 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -75,7 +75,7 @@ class _SelectDoctorPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.chooseDoctor.tr(), + title: LocaleKeys.chooseDoctor.tr(context: context), // bottomChild: Container( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), // padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 8fa72d4..3d12f03 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; @@ -144,13 +145,14 @@ class _AppointmentCalendarState extends State { child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here - SizedBox( - height: 100.h, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.start, + dayEvents.isNotEmpty + ? SizedBox( + height: 100.h, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, spacing: 6.h, runSpacing: 6.h, children: List.generate( @@ -168,10 +170,13 @@ class _AppointmentCalendarState extends State { ), ), ), - ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)), + SizedBox(height: 16.h), CustomButton( text: LocaleKeys.select.tr(context: context), + isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ @@ -186,7 +191,7 @@ class _AppointmentCalendarState extends State { ); } else { bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); - bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); + bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); Navigator.of(context).pop(); Navigator.of(context).push( CustomPageRoute( @@ -304,11 +309,14 @@ class _AppointmentCalendarState extends State { Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; timeList.add(timeSlot); } - selectedTime = dayEvents[selectedButtonIndex].isoTime!; + if (dayEvents.isNotEmpty) { + selectedTime = dayEvents[selectedButtonIndex].isoTime!; + } + setState(() {}); } void _onDaySelected(DateTime day) { - final DateFormat formatter = DateFormat('yyyy-MM-dd'); + final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); setState(() { selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); From 102690b2b8c445db0afff85f1081c8049710b43e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 9 Feb 2026 10:41:16 +0300 Subject: [PATCH 2/6] update --- ios/Runner.xcodeproj/project.pbxproj | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7a41ae2..dd2d96e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -18,12 +18,12 @@ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; }; - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; }; - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; }; - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; }; + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; }; + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; }; + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; @@ -41,15 +41,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */, - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */, - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */, + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */, + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */, + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -73,9 +73,9 @@ 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 = ""; }; - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A802F35CD730003FF7D /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; 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 = ""; }; @@ -95,9 +95,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */, - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */, - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */, + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */, + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */, + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -136,9 +136,9 @@ 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */, - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */, - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */, + 765A5A802F35CD730003FF7D /* Penguin.xcframework */, + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */, + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -235,10 +235,10 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); @@ -524,8 +524,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -710,8 +710,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -739,8 +739,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; From 2c7f4ddc48cd0f9e19065a3924b8160c2daab7c4 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 9 Feb 2026 11:12:56 +0300 Subject: [PATCH 3/6] Ai Analysis & PMA-(1078,1079) --- assets/images/svg/ai_overview.svg | 19 + assets/images/svg/calculator_icon.svg | 3 + assets/images/svg/guard_icon.svg | 5 + assets/images/svg/high_caution.svg | 5 + assets/images/svg/information-circle.svg | 3 + assets/langs/ar-SA.json | 44 +- assets/langs/en-US.json | 42 +- lib/core/api_consts.dart | 2 + lib/core/app_assets.dart | 5 + .../authentication_view_model.dart | 11 +- lib/features/lab/lab_repo.dart | 103 +++- lib/features/lab/lab_view_model.dart | 245 ++++---- .../ai_overview_labresult_model_payload.dart | 69 +++ .../ai_overview_labresults_by_ai_model.dart | 177 ++++++ .../lab/models/resp_models/lab_result.dart | 4 + ...atient_lab_order_response_by_ai_model.dart | 145 +++++ lib/generated/locale_keys.g.dart | 38 ++ .../authentication/saved_login_screen.dart | 1 + .../book_appointment/select_clinic_page.dart | 94 ++- .../health_calculator_detailed_page.dart | 39 +- .../health_calculator_view_model.dart | 1 + .../widgets/bf.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 2 +- .../LabResultByClinic.dart | 27 +- .../ai_analysis_widget.dart | 100 ++++ .../lab/lab_results/lab_result_details.dart | 136 ++--- .../lab_ai_analysis_detailed_page.dart | 538 ++++++++++++++++++ lib/services/dialog_service.dart | 21 +- lib/theme/colors.dart | 2 + lib/widgets/appbar/collapsing_list_view.dart | 112 ++-- 30 files changed, 1704 insertions(+), 291 deletions(-) create mode 100644 assets/images/svg/ai_overview.svg create mode 100644 assets/images/svg/calculator_icon.svg create mode 100644 assets/images/svg/guard_icon.svg create mode 100644 assets/images/svg/high_caution.svg create mode 100644 assets/images/svg/information-circle.svg create mode 100644 lib/features/lab/models/ai_overview_labresult_model_payload.dart create mode 100644 lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart create mode 100644 lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart create mode 100644 lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart create mode 100644 lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart diff --git a/assets/images/svg/ai_overview.svg b/assets/images/svg/ai_overview.svg new file mode 100644 index 0000000..95c29aa --- /dev/null +++ b/assets/images/svg/ai_overview.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/calculator_icon.svg b/assets/images/svg/calculator_icon.svg new file mode 100644 index 0000000..989a4db --- /dev/null +++ b/assets/images/svg/calculator_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/guard_icon.svg b/assets/images/svg/guard_icon.svg new file mode 100644 index 0000000..e3a68d9 --- /dev/null +++ b/assets/images/svg/guard_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/high_caution.svg b/assets/images/svg/high_caution.svg new file mode 100644 index 0000000..d268468 --- /dev/null +++ b/assets/images/svg/high_caution.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/information-circle.svg b/assets/images/svg/information-circle.svg new file mode 100644 index 0000000..f1a850e --- /dev/null +++ b/assets/images/svg/information-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 178b4c0..3df6041 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1526,8 +1526,48 @@ "trackerAndOthers": "المتتبع وأخرى", "bmi": "مؤشر كتلة الجسم", "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", - "noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة" - "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة" + "noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة", + "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "aiOverView": "AI ملخص", + "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", + "aiAnalysis": "AI تحليل", + "prediction": "تنبؤ", + "prevention": "وقاية", + "disclaimer": "تنصل", + "thisAboveInfo": "المعلومات المذكورة أعلاه تم إنشاؤها بواسطة AI", + "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", + "consent": "موافقة", + "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", + "bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.", + "bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.", + "caloriesMaintenanceDescription": "السعرات الحرارية اليومية المُقدَّرة للحفاظ على وزنك الحالي بناءً على مستوى نشاطك ومعدل الأيض لديك. استخدم هذه الكمية كخط أساس: زدها قليلاً لزيادة الكتلة العضلية، أو قللها قليلاً لإنقاص الوزن تدريجياً. تابع تقدمك وعدّل الخطة كل أسبوعين إلى أربعة أسابيع.", + "caloriesModerateDeficitDescription": "نظام غذائي معتدل لتقليل السعرات الحرارية مصمم لإنقاص الوزن بشكل ثابت ومستدام (عادةً من 0.25 إلى 0.5 كيلوغرام أسبوعيًا). يُنصح بدمجه مع تمارين المقاومة وتناول كمية كافية من البروتين للحفاظ على الكتلة العضلية. تجنب التخفيضات الحادة في السعرات الحرارية واستشر طبيبًا مختصًا إذا كنت تعاني من أي حالة صحية.", + "caloriesAggressiveDeficitDescription": "يؤدي خفض السعرات الحرارية بشكل كبير إلى فقدان الوزن بشكل أسرع، ولكنه قد يصعب الاستمرار عليه ويزيد من خطر نقص العناصر الغذائية والإرهاق وفقدان الكتلة العضلية. لا يُنصح باتباعه على المدى الطويل؛ استشر طبيبًا أو أخصائي تغذية قبل الاستمرار.", + "bodyFatEssentialDescription": "نسبة الدهون في جسمك ضمن النطاق الأساسي/الرياضي. هذا المستوى شائع لدى الرياضيين ويشير إلى انخفاض نسبة الدهون في الجسم. مع أن هذا قد يكون مثاليًا للأداء، تأكد من الحفاظ على تغذية سليمة ووظائف هرمونية متوازنة. استشر طبيبًا إذا شعرت بالتعب، أو واجهت مشاكل هرمونية، أو أي مخاوف صحية أخرى.", + "bodyFatFitnessDescription": "نسبة الدهون في جسمك ضمن النطاق الصحي. وهذا يدل على مستوى صحي من الدهون في الجسم يرتبط بلياقة بدنية جيدة. حافظ على نمط حياتك الحالي، بما في ذلك ممارسة الرياضة بانتظام واتباع نظام غذائي متوازن، للحفاظ على هذا المستوى الصحي.", + "bodyFatAverageDescription": "نسبة الدهون في جسمك ضمن المعدل الطبيعي/المقبول، وهي نسبة تُعتبر صحية لعموم الناس. لتحسين تكوين جسمك، يُنصح بممارسة تمارين القوة، وتمارين القلب والأوعية الدموية، واتباع نظام غذائي متوازن. ركّز على التغييرات التدريجية والمستدامة للحفاظ على صحتك على المدى الطويل.", + "bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.", + "bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.", + "bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.", + "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط \u200B\u200Bبالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "يعتمد السياق", + "calculatedValue": "القيمة المحسوبة", + "calculationFormula": "صيغة الحساب" + + } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index d6f5228..7799807 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1517,8 +1517,46 @@ "trackerAndOthers": "Tracker & Others", "bmi": "BMI", "yesPleaseINeedHelp": "Yes, Please I need help", - "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic" - "unableToSendOTP": "Unable to send Verification OTP" + "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", + "unableToSendOTP": "Unable to send Verification OTP", + "aiOverView": "AI Overview", + "generateAiAnalysis": "Generate AI analysis for this order", + "aiAnalysis": "AI Analysis", + "prediction": "Prediction", + "prevention": "Prevention", + "disclaimer": "Disclaimer", + "thisAboveInfo": "The above information is generated by", + "aiDisclaimer": "Your lab result data will be securely shared with our AI service for analysis. This helps provide personalized health insights. Do you want to proceed?", + "consent": "Consent", + "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", + "bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.", + "bmiOverweightDescription": "BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.", + "caloriesMaintenanceDescription": "Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.", + "caloriesModerateDeficitDescription": "A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.", + "caloriesAggressiveDeficitDescription": "A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.", + "bodyFatEssentialDescription": "Your body fat percentage is in the essential/athletic range. This level is typical for athletes and indicates very low body fat. While this may be optimal for performance, ensure you maintain adequate nutrition and hormone function. Consult a healthcare professional if you experience fatigue, hormonal issues, or other health concerns.", + "bodyFatFitnessDescription": "Your body fat percentage is in the fitness range. This indicates a healthy level of body fat associated with good physical conditioning. Maintain your current lifestyle habits including regular exercise and balanced nutrition to sustain this healthy range.", + "bodyFatAverageDescription": "Your body fat percentage is in the average/acceptable range. This is considered a healthy range for general population. To improve body composition, consider incorporating strength training, cardiovascular exercise, and a balanced diet. Focus on gradual, sustainable changes for long-term health.", + "bodyFatObeseDescription": "Your body fat percentage is above the recommended healthy range. This may increase health risks including cardiovascular disease, diabetes, and joint problems. Consider consulting a healthcare provider or registered dietitian to develop a personalized plan that includes balanced nutrition, regular physical activity, and lifestyle modifications for sustainable weight management.", + "bmrNormalDescription": "Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.", + "bmrLowDescription": "Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.", + "bmrHighDescription": "Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "Context Dependent", + "calculatedValue": "Calculated Value", + "calculationFormula": "Calculation Formula" diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 8319c9d..e71629d 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -901,6 +901,8 @@ class ApiConsts { static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; + static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // ************ static values for Api **************** static final double appVersionID = 50.3; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 5b8d2ed..d9d7120 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -251,6 +251,10 @@ class AppAssets { static const String alertSquare = '$svgBasePath/alert-square.svg'; static const String arrowRight = '$svgBasePath/arrow-right.svg'; static const String tickIcon = '$svgBasePath/tick.svg'; + static const String guradIcon = '$svgBasePath/guard_icon.svg'; + static const String calculatorIcon = '$svgBasePath/calculator_icon.svg'; + static const String informationIcon = '$svgBasePath/information-circle.svg'; + static const String highCautionIcon = '$svgBasePath/high_caution.svg'; // Symptoms Checker static const String calendarGrey = '$svgBasePath/calendar-grey.svg'; @@ -322,6 +326,7 @@ class AppAssets { static const String doctorResponseIcon = '$svgBasePath/doctor_response_icon.svg'; static const String changeLanguageHomePageIcon = '$svgBasePath/change_language_home_page.svg'; + static const String aiOverView = '$svgBasePath/ai_overview.svg'; // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8206160..e93c246 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -279,9 +279,10 @@ class AuthenticationViewModel extends ChangeNotifier { loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; - if (phoneNumberController.text.isEmpty) { - phoneNumberController.text = "504278212"; - } + // if (phoneNumberController.text.isEmpty) { + // phoneNumberController.text = "504278212"; + // } + bool isValidated = ValidationUtils.isValidatePhoneAndId(phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text); if (!isValidated) { @@ -404,7 +405,6 @@ class AuthenticationViewModel extends ChangeNotifier { return; } patientShareRequestID = apiResponse.data['PatientShareRequestID']; - print("PatientShareRequestID ==============: $patientShareRequestID"); _appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID']; } if (apiResponse.messageStatus == 2) { @@ -581,7 +581,8 @@ class AuthenticationViewModel extends ChangeNotifier { if (!_appState.getIsChildLoggedIn) { await medicalVm.getFamilyFiles(status: 0); // await medicalVm.getAllPendingRecordsByResponseId(); - _navigationService.replaceAllRoutesAndNavigateToLanding(); + _navigationService.popUntilNamed(AppRoutes.landingScreen); + // _navigationService.replaceAllRoutesAndNavigateToLanding(); } } else { if (activation.list != null && activation.list!.isNotEmpty) { diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index e661157..a4cdcf1 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/features/lab/models/resp_models/patient_lab_special_result.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -13,6 +15,7 @@ import 'models/resp_models/lab_result.dart' show LabResult; abstract class LabRepo { Future>>> getPatientLabOrders(); + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus); @@ -22,6 +25,10 @@ abstract class LabRepo { Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); + + Future>> getPatientAiOverViewLabOrders(var payload); + + Future>> getPatientAiOverViewLabOrder(var payload); } class LabRepoImp implements LabRepo { @@ -71,10 +78,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> getPatientLabResults( - PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName - ) async { - + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -100,10 +104,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => LabResult.fromJson(item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => LabResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -125,9 +126,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getPatientLabResultsByHospitals( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -175,9 +174,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getSpecialLabResult( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getSpecialLabResult(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -203,11 +200,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => PatientLabSpecialResult.fromJson( - item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => PatientLabSpecialResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -318,4 +311,76 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getPatientAiOverViewLabOrder(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrder, + body: labOrder, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + var res; + if (response["analysis_result"] != null) { + res = response["analysis_result"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrderResponseByAi.fromJson(res), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getPatientAiOverViewLabOrders(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrders, + body: {"lab_results": labOrder}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + var res; + if (response["lab_analysis_result"] != null) { + res = response["lab_analysis_result"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrdersResponseByAi.fromJson(res), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 118c54a0..9b4e941 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,16 +1,24 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:core'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.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/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/models/ai_overview_labresult_model_payload.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart'; +import 'package:hmg_patient_app_new/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -53,20 +61,7 @@ class LabViewModel extends ChangeNotifier { List mainLabResults = []; List mainGraphPoints = []; List filteredGraphValues = []; - List months = [ - 'Jan', - 'Feb', - 'Mar', - 'April', - 'May', - 'Jun', - 'July', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ]; + List months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'Jun', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; late List _labSuggestionsList = []; @@ -86,22 +81,22 @@ class LabViewModel extends ChangeNotifier { List> patientLabOrdersByHospital = []; List> patientLabOrdersViewList = []; - LabViewModel( - {required this.labRepo, - required this.errorHandlerService, - required this.navigationService}); + LabOrderResponseByAi? labOrderResponseByAi; + LabOrdersResponseByAi? labOrdersResponseByAi; + + LabViewModel({required this.labRepo, required this.errorHandlerService, required this.navigationService}); initLabProvider() { // if (isLabNeedToLoad) { - patientLabOrders.clear(); - filteredLabOrders.clear(); - labOrderTests.clear(); - isLabOrdersLoading = true; - isLabResultsLoading = true; - patientLabOrdersByClinic.clear(); - patientLabOrdersByHospital.clear(); - patientLabOrdersViewList.clear(); - getPatientLabOrders(); + patientLabOrders.clear(); + filteredLabOrders.clear(); + labOrderTests.clear(); + isLabOrdersLoading = true; + isLabResultsLoading = true; + patientLabOrdersByClinic.clear(); + patientLabOrdersByHospital.clear(); + patientLabOrdersViewList.clear(); + getPatientLabOrders(); // } notifyListeners(); } @@ -133,13 +128,13 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getPatientLabOrders(); result.fold( - (failure) async { - isLabOrdersLoading = false; - isLabResultsLoading = false; - notifyListeners(); - }, - // => await errorHandler_service.handleError(failure: failure), - (apiResponse) { + (failure) async { + isLabOrdersLoading = false; + isLabResultsLoading = false; + notifyListeners(); + }, + // => await errorHandler_service.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -203,11 +198,7 @@ class LabViewModel extends ChangeNotifier { } filterSuggestions() { - final List labels = patientLabOrders - .expand((order) => order.testDetails ?? []) - .map((detail) => detail.description) - .whereType() - .toList(); + final List labels = patientLabOrders.expand((order) => order.testDetails ?? []).map((detail) => detail.description).whereType().toList(); _labSuggestionsList = labels.toSet().toList(); notifyListeners(); @@ -319,14 +310,12 @@ class LabViewModel extends ChangeNotifier { ); } - Future getPatientLabResultByHospital( - PatientLabOrdersResponseModel laborder) async { + Future getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async { isLabResultByHospitalLoading = true; notifyListeners(); mainLabResultsByHospitals.clear(); - final result = await labRepo.getPatientLabResultsByHospitals(laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); + final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); result.fold( (failure) async { @@ -350,12 +339,9 @@ class LabViewModel extends ChangeNotifier { mainLabResults.clear(); filteredGraphValues.clear(); maxY = double.negativeInfinity; - minY = double.infinity; - maxX = double.infinity; - final result = await labRepo.getPatientLabResults( - laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), - procedureName); + minY = double.infinity; + maxX = double.infinity; + final result = await labRepo.getPatientLabResults(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), procedureName); result.fold( (failure) async { @@ -369,7 +355,6 @@ class LabViewModel extends ChangeNotifier { var recentThree = sort(apiResponse.data!); mainLabResults = recentThree; - double highRefrenceValue = double.negativeInfinity; String? flagForHighReferenceRange; double lowRefenceValue = double.infinity; @@ -377,11 +362,10 @@ class LabViewModel extends ChangeNotifier { recentThree.reversed.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); - if (resultValue>maxY) { + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); + if (resultValue > maxY) { maxY = resultValue; maxX = maxY; } @@ -398,14 +382,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues.add(DataPoint( - value: transformedValue, - actualValue:element.resultValue!, - label: formatDateAsMMYY(dateTime), - unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - referenceValue: element.calculatedResultFlag ?? "", - + value: transformedValue, + actualValue: element.resultValue!, + label: formatDateAsMMYY(dateTime), + unitOfMeasurement: element.uOM, + displayTime: resultDate(dateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), + referenceValue: element.calculatedResultFlag ?? "", )); } catch (e) {} }); @@ -421,7 +404,7 @@ class LabViewModel extends ChangeNotifier { this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(1)); this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(1)); - if(maxY< highRefrenceValue) { + if (maxY < highRefrenceValue) { maxY = highRefrenceValue; } maxY += 25; @@ -431,7 +414,9 @@ class LabViewModel extends ChangeNotifier { checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); // filteredGraphValues = [filteredGraphValues.first]; - navigationService.push(MaterialPageRoute( + labOrderResponseByAi = null; + navigationService.push( + MaterialPageRoute( builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription), ), ); @@ -441,14 +426,13 @@ class LabViewModel extends ChangeNotifier { ); } - void checkIfGraphShouldBeDisplayed(LabResult recentResult){ + void checkIfGraphShouldBeDisplayed(LabResult recentResult) { shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed(); isGraphVisible = shouldShowGraph; notifyListeners(); } - Future getPatientSpecialResult( - PatientLabOrdersResponseModel laborder) async { + Future getPatientSpecialResult(PatientLabOrdersResponseModel laborder) async { isSpecialResultsLoading = true; labSpecialResult = ""; notifyListeners(); @@ -470,8 +454,7 @@ class LabViewModel extends ChangeNotifier { StringBuffer htmlbuffer = StringBuffer(""); apiResponse.data?.forEach((element) { - if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) - htmlbuffer.write("${element.resultDataHTML}

"); + if (element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) htmlbuffer.write("${element.resultDataHTML}

"); }); labSpecialResult = htmlbuffer.toString(); @@ -483,10 +466,8 @@ class LabViewModel extends ChangeNotifier { ); } - String resultDate(DateTime date){ - - - return '${date.day} ${months[date.month-1]},${date.year}'; + String resultDate(DateTime date) { + return '${date.day} ${months[date.month - 1]},${date.year}'; } double transformValueInRange(double inputValue, String flag) { @@ -494,7 +475,7 @@ class LabViewModel extends ChangeNotifier { double rangeStart, rangeEnd; switch (flag) { - case'LCL': + case 'LCL': case 'CL': rangeStart = 0.0; rangeEnd = 19.0; @@ -531,16 +512,17 @@ class LabViewModel extends ChangeNotifier { debugPrint("the transformed value is $transformedValue"); return transformedValue; } + void getSelectedDateRange(DateTime? start, DateTime? end) { maxY = double.negativeInfinity; - if(start == null && end == null) { + if (start == null && end == null) { mainLabResults.forEach((element) { final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!); - try{ + try { var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } @@ -549,54 +531,43 @@ class LabViewModel extends ChangeNotifier { actualValue: element.resultValue!, label: formatDateAsMMYY(time), unitOfMeasurement: element.uOM, - displayTime: resultDate(time), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "", )); - }catch(e){ - - } + } catch (e) {} }); - - }else { + } else { filteredGraphValues.clear(); mainLabResults.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } if (start != null && end == null) { if (dateTime.isAtSameMomentAs(start)) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, unitOfMeasurement: element.uOM, - label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } else if (start != null && end != null) { if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(dateTime), unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } @@ -605,16 +576,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList(); - notifyListeners(); } String formatDateAsMMYY(DateTime date) { - - return '${months[date.month-1]}, ${date.year}'; + return '${months[date.month - 1]}, ${date.year}'; } - List sortByFlagAndValue(List original) { const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH']; @@ -648,7 +616,6 @@ class LabViewModel extends ChangeNotifier { } List sort(List original) { - final copy = List.from(original); copy.sort((a, b) { final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime); @@ -663,8 +630,6 @@ class LabViewModel extends ChangeNotifier { } List sortFilteredList(List original) { - - final copy = List.from(original); copy.sort((a, b) { final aDate = a.time; @@ -699,7 +664,7 @@ class LabViewModel extends ChangeNotifier { } } - String getFormattedDate(DateTime date){ + String getFormattedDate(DateTime date) { return DateFormat('EEEE, dd MMMM. yyyy').format(date); } @@ -713,7 +678,7 @@ class LabViewModel extends ChangeNotifier { case 'N': return AppAssets.ic_normal_result; case 'H': - return AppAssets.ic_low_result; + return AppAssets.ic_low_result; case 'CH': case 'HCH': return AppAssets.ic_critical_low_result; @@ -755,8 +720,80 @@ class LabViewModel extends ChangeNotifier { } } - alterGraphVisibility(){ + alterGraphVisibility() { isGraphVisible = !isGraphVisible; notifyListeners(); } + + Future getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder}) async { + LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic"); + List> results = []; + Map orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []}; + List> testResults = []; + if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) { + for (var testDetail in labOrder.testDetails!) { + LabResult? matchingResult = + mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult()); + testResults.add({ + "test_name": testDetail.description ?? "", + "result_value": matchingResult.resultValue ?? "", + "reference_range": matchingResult.referanceRange ?? "", + "unit": matchingResult.uOM ?? "", + "flag": matchingResult.calculatedResultFlag ?? "N" + }); + } + } + orderData["results"] = testResults; + results.add(orderData); + final result = await labRepo.getPatientAiOverViewLabOrders(results); + result.fold( + (failure) async { + LoadingUtils.hideFullScreenLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoadingUtils.hideFullScreenLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrdersResponseByAi = apiResponse.data; + navigationService.push( + MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()), + ); + } + }, + ); + + LoadingUtils.hideFullScreenLoader(); + } + + Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult}) async { + LoaderBottomSheet.showLoader(); + List> results = []; + results.add({ + "Description": recentLabResult.description ?? '', + "ResultValue": recentLabResult.resultValue ?? '', + "CalculatedResultFlag": recentLabResult.calculatedResultFlag ?? '', + "UOM": recentLabResult.uOM ?? '', + "ReferanceRange": recentLabResult.referanceRange ?? '', + }); + + var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results}; + final result = await labRepo.getPatientAiOverViewLabOrder(payload); + result.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoaderBottomSheet.hideLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrderResponseByAi = apiResponse.data; + notifyListeners(); + } + }, + ); + + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/features/lab/models/ai_overview_labresult_model_payload.dart b/lib/features/lab/models/ai_overview_labresult_model_payload.dart new file mode 100644 index 0000000..c876592 --- /dev/null +++ b/lib/features/lab/models/ai_overview_labresult_model_payload.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +class AiLabOrdersOverView { + String? orderDate; + String? clinic; + String? doctor; + List? results; + + AiLabOrdersOverView({ + this.orderDate, + this.clinic, + this.doctor, + this.results, + }); + + factory AiLabOrdersOverView.fromRawJson(String str) => AiLabOrdersOverView.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiLabOrdersOverView.fromJson(Map json) => AiLabOrdersOverView( + orderDate: json["order_date"] == null ? null : json["order_date"], + clinic: json["clinic"], + doctor: json["doctor"], + results: json["results"] == null ? [] : List.from(json["results"]!.map((x) => Result.fromJson(x))), + ); + + Map toJson() => { + "order_date": orderDate, + "clinic": clinic, + "doctor": doctor, + "results": results == null ? [] : List.from(results!.map((x) => x.toJson())), + }; +} + +class Result { + String? testName; + String? resultValue; + String? referenceRange; + String? unit; + String? flag; + + Result({ + this.testName, + this.resultValue, + this.referenceRange, + this.unit, + this.flag, + }); + + factory Result.fromRawJson(String str) => Result.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Result.fromJson(Map json) => Result( + testName: json["test_name"], + resultValue: json["result_value"], + referenceRange: json["reference_range"], + unit: json["unit"], + flag: json["flag"], + ); + + Map toJson() => { + "test_name": testName, + "result_value": resultValue, + "reference_range": referenceRange, + "unit": unit, + "flag": flag, + }; +} diff --git a/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart new file mode 100644 index 0000000..2353cf8 --- /dev/null +++ b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart @@ -0,0 +1,177 @@ +import 'dart:convert'; + +class LabOrdersResponseByAi { + Analysis? analysis; + String? patientId; + int? totalOrders; + int? totalResults; + + LabOrdersResponseByAi({ + this.analysis, + this.patientId, + this.totalOrders, + this.totalResults, + }); + + factory LabOrdersResponseByAi.fromRawJson(String str) => LabOrdersResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrdersResponseByAi.fromJson(Map json) => LabOrdersResponseByAi( + analysis: json["analysis"] == null ? null : Analysis.fromJson(json["analysis"]), + patientId: json["patient_id"], + totalOrders: json["total_orders"], + totalResults: json["total_results"], + ); + + Map toJson() => { + "analysis": analysis?.toJson(), + "patient_id": patientId, + "total_orders": totalOrders, + "total_results": totalResults, + }; +} + +class Analysis { + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? prevention; + String? rawMessage; + List? riskFactors; + List? suggestedDoctors; + String? summary; + + Analysis({ + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.prevention, + this.rawMessage, + this.riskFactors, + this.suggestedDoctors, + this.summary, + }); + + factory Analysis.fromRawJson(String str) => Analysis.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Analysis.fromJson(Map json) => Analysis( + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + rawMessage: json["raw_message"], + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + summary: json["summary"], + ); + + Map toJson() => { + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "raw_message": rawMessage, + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "summary": summary, + }; +} + +class AiGeneratedAttribute { + String? calculatedResultFlag; + String? description; + String? referanceRange; + String? resultValue; + String? testShortDescription; + String? uom; + String? calculationFormula; + + AiGeneratedAttribute({ + this.calculatedResultFlag, + this.description, + this.referanceRange, + this.resultValue, + this.testShortDescription, + this.uom, + this.calculationFormula, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + calculatedResultFlag: json["CalculatedResultFlag"], + description: json["Description"], + referanceRange: json["ReferanceRange"], + resultValue: json["ResultValue"], + testShortDescription: json["TestShortDescription"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + ); + + Map toJson() => { + "CalculatedResultFlag": calculatedResultFlag, + "Description": description, + "ReferanceRange": referanceRange, + "ResultValue": resultValue, + "TestShortDescription": testShortDescription, + "UOM": uom, + "calculationFormula": calculationFormula, + }; +} + +class AiRecommendation { + String? priority; + String? recommendation; + + AiRecommendation({ + this.priority, + this.recommendation, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + priority: json["priority"], + recommendation: json["recommendation"], + ); + + Map toJson() => { + "priority": priority, + "recommendation": recommendation, + }; +} + +class RiskFactor { + String? description; + String? factor; + String? risk; + + RiskFactor({ + this.description, + this.factor, + this.risk, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + description: json["description"], + factor: json["factor"], + risk: json["risk"], + ); + + Map toJson() => { + "description": description, + "factor": factor, + "risk": risk, + }; +} diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart index a075be3..2314684 100644 --- a/lib/features/lab/models/resp_models/lab_result.dart +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -26,6 +26,7 @@ class LabResult { String? referenceLow; num? resultTypeID; String? packageShortDescription; + int? testId; LabResult( {this.description, @@ -52,6 +53,7 @@ class LabResult { this.referenceHigh, this.criticalLow, this.referenceLow, + this.testId, }); LabResult.fromJson(Map json) { @@ -82,6 +84,7 @@ class LabResult { referenceLow = json['ReferenceLow']; packageShortDescription = json['PackageShortDescription']; resultTypeID = json['ResultTypeID']; + testId = json['TestID']; } Map toJson() { @@ -110,6 +113,7 @@ class LabResult { data['ReferenceHigh'] = this.referenceHigh; data['CriticalLow'] = this.criticalLow; data['ReferenceLow'] = this.referenceLow; + data['TestID'] = this.testId; return data; } diff --git a/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart new file mode 100644 index 0000000..7ad3d54 --- /dev/null +++ b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart @@ -0,0 +1,145 @@ +import 'dart:convert'; + +class LabOrderResponseByAi { + String? summary; + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? riskFactors; + List? prevention; + List? suggestedDoctors; + String? rawMessage; + + LabOrderResponseByAi({ + this.summary, + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.riskFactors, + this.prevention, + this.suggestedDoctors, + this.rawMessage, + }); + + factory LabOrderResponseByAi.fromRawJson(String str) => LabOrderResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrderResponseByAi.fromJson(Map json) => LabOrderResponseByAi( + summary: json["summary"], + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + rawMessage: json["raw_message"], + ); + + Map toJson() => { + "summary": summary, + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "raw_message": rawMessage, + }; +} + +class AiGeneratedAttribute { + String? description; + String? resultValue; + String? referanceRange; + String? calculatedResultFlag; + String? uom; + String? calculationFormula; + String? testShortDescription; + + AiGeneratedAttribute({ + this.description, + this.resultValue, + this.referanceRange, + this.calculatedResultFlag, + this.uom, + this.calculationFormula, + this.testShortDescription, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + description: json["Description"], + resultValue: json["ResultValue"], + referanceRange: json["ReferanceRange"], + calculatedResultFlag: json["CalculatedResultFlag"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + testShortDescription: json["TestShortDescription"], + ); + + Map toJson() => { + "Description": description, + "ResultValue": resultValue, + "ReferanceRange": referanceRange, + "CalculatedResultFlag": calculatedResultFlag, + "UOM": uom, + "calculationFormula": calculationFormula, + "TestShortDescription": testShortDescription, + }; +} + +class AiRecommendation { + String? recommendation; + String? priority; + + AiRecommendation({ + this.recommendation, + this.priority, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + recommendation: json["recommendation"], + priority: json["priority"], + ); + + Map toJson() => { + "recommendation": recommendation, + "priority": priority, + }; +} + +class RiskFactor { + String? factor; + String? risk; + String? description; + + RiskFactor({ + this.factor, + this.risk, + this.description, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + factor: json["factor"], + risk: json["risk"], + description: json["description"], + ); + + Map toJson() => { + "factor": factor, + "risk": risk, + "description": description, + }; +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 416d2a4..05563f7 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1515,5 +1515,43 @@ abstract class LocaleKeys { static const yesPleaseINeedHelp = 'yesPleaseINeedHelp'; static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; static const unableToSendOTP = 'unableToSendOTP'; + static const aiOverView = 'aiOverView'; + static const generateAiAnalysis = 'generateAiAnalysis'; + static const aiAnalysis = 'aiAnalysis'; + static const prediction = 'prediction'; + static const prevention = 'prevention'; + static const disclaimer = 'disclaimer'; + static const thisAboveInfo = 'thisAboveInfo'; + static const aiDisclaimer = 'aiDisclaimer'; + static const consent = 'consent'; + static const bmiNormalDescription = 'bmiNormalDescription'; + static const bmiUnderweightDescription = 'bmiUnderweightDescription'; + static const bmiOverweightDescription = 'bmiOverweightDescription'; + static const caloriesMaintenanceDescription = 'caloriesMaintenanceDescription'; + static const caloriesModerateDeficitDescription = 'caloriesModerateDeficitDescription'; + static const caloriesAggressiveDeficitDescription = 'caloriesAggressiveDeficitDescription'; + static const bodyFatEssentialDescription = 'bodyFatEssentialDescription'; + static const bodyFatFitnessDescription = 'bodyFatFitnessDescription'; + static const bodyFatAverageDescription = 'bodyFatAverageDescription'; + static const bodyFatObeseDescription = 'bodyFatObeseDescription'; + static const bmrNormalDescription = 'bmrNormalDescription'; + static const bmrLowDescription = 'bmrLowDescription'; + static const bmrHighDescription = 'bmrHighDescription'; + static const ibwAtTargetDescription = 'ibwAtTargetDescription'; + static const ibwBelowTargetDescription = 'ibwBelowTargetDescription'; + static const ibwAboveTargetDescription = 'ibwAboveTargetDescription'; + static const ibwCloseToRangeDescription = 'ibwCloseToRangeDescription'; + static const ibwUnderweightDescription = 'ibwUnderweightDescription'; + static const ibwUnderweightGenericDescription = 'ibwUnderweightGenericDescription'; + static const ibwOverweightDescription = 'ibwOverweightDescription'; + static const ibwOverweightGenericDescription = 'ibwOverweightGenericDescription'; + static const dietVeryLowCarbsDescription = 'dietVeryLowCarbsDescription'; + static const dietLowCarbsDescription = 'dietLowCarbsDescription'; + static const dietModerateCarbsDescription = 'dietModerateCarbsDescription'; + static const dietUSDAGuidelinesDescription = 'dietUSDAGuidelinesDescription'; + static const dietZoneDietDescription = 'dietZoneDietDescription'; + static const contextDependent = 'contextDependent'; + static const calculatedValue = 'calculatedValue'; + static const calculationFormula = 'calculationFormula'; } diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index f0fd8cb..22fc467 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -115,6 +115,7 @@ class _SavedLogin extends State { CustomButton( text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { authVm.loginWithFingerPrintFace(() {}); } else { diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index d96c9da..9d2c292 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -261,8 +261,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.', + description: LocaleKeys.bmiNormalDescription.tr(), color: Colors.green, icon: Icons.scale, hide: true, @@ -272,8 +271,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.', + description: LocaleKeys.bmiUnderweightDescription.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -283,8 +281,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.', + description: LocaleKeys.bmiOverweightDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -295,8 +292,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.', + description: LocaleKeys.caloriesMaintenanceDescription.tr(), color: Colors.green, icon: Icons.scale, ), @@ -305,8 +301,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.', + description: LocaleKeys.caloriesModerateDeficitDescription.tr(), color: Colors.orange, icon: Icons.trending_down, ), @@ -315,19 +310,59 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.', + description: LocaleKeys.caloriesAggressiveDeficitDescription.tr(), color: Colors.red, icon: Icons.warning_rounded, ), ] + ] else if (type == HealthCalculatorsTypeEnum.bodyFat) ...[ + if (calculatedResult["fatCategory"] == "Essential Fat" || calculatedResult["fatCategory"] == "Athletes") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatEssentialDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Fitness") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatFitnessDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Average" || calculatedResult["fatCategory"] == "Acceptable") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatAverageDescription.tr(), + color: Colors.orange, + hide: true, + icon: Icons.accessibility_new, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Obese" || calculatedResult["fatCategory"] == "Overweight") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatObeseDescription.tr(), + color: Colors.red, + hide: true, + icon: Icons.warning_rounded, + ), + SizedBox(height: 16.h), + ] ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ if (calculatedResult["bmrRange"] == "Normal") ...[ calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.', + description: LocaleKeys.bmrNormalDescription.tr(), color: Colors.green, hide: true, icon: Icons.scale, @@ -337,8 +372,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.', + description: LocaleKeys.low.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -348,8 +382,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.', + description: LocaleKeys.bmrHighDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -915,9 +948,16 @@ class _SelectClinicPageState extends State { Widget _buildStatusIcon(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); @@ -928,9 +968,16 @@ class _SelectClinicPageState extends State { Color getTextColor(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return AppColors.successColor; - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return AppColors.warningColor; } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return AppColors.errorColor; @@ -1156,7 +1203,8 @@ class _SelectClinicPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), ], ); }, diff --git a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index 4a83f51..8cccf03 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -52,8 +52,8 @@ class _HealthCalculatorDetailedPageState extends State(); Navigator.of(context).push(CustomPageRoute( page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult), )); @@ -174,69 +172,58 @@ class _HealthCalculatorDetailedPageState extends State { children: [ Radio( value: unit, - groupValue: selectedHeightUnit, + groupValue: selectedHipUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { if (value == null) return; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 006280f..a6ee154 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; 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'; 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 { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, 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: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[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: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), 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: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // 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, // fontWeight: FontWeight.w500, // 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, // fontWeight: FontWeight.w500, // 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: () { model.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, fontWeight: FontWeight.w500, 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.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, 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: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'; 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 { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, 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: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[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: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), 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: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // 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, // fontWeight: FontWeight.w500, // 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, // fontWeight: FontWeight.w500, // 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: () { model.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, fontWeight: FontWeight.w500, 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.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, 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/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index 50fd1f1..954c09b 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -1,17 +1,22 @@ -import 'package:easy_localization/easy_localization.dart' - show tr, StringTranslateExtension; +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/LabResultList.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -25,6 +30,7 @@ class LabResultByClinic extends StatelessWidget { late LabViewModel labViewModel; PatientLabOrdersResponseModel labOrder; + @override Widget build(BuildContext context) { labViewModel = Provider.of(context, listen: false); @@ -35,6 +41,21 @@ class LabResultByClinic extends StatelessWidget { Expanded( child: CollapsingListView( title: LocaleKeys.labResults.tr(), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewLabOrders(labOrder: labOrder); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 8.h, @@ -77,7 +98,7 @@ class LabResultByClinic extends StatelessWidget { } }, ), - LabOrderSpecialResult() + LabOrderSpecialResult(), ], ).paddingAll(24.h), )), diff --git a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart new file mode 100644 index 0000000..a2d0ff6 --- /dev/null +++ b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart @@ -0,0 +1,100 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class AiAnalysisWidget extends StatelessWidget { + final LabOrderResponseByAi data; + + const AiAnalysisWidget({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient), + padding: EdgeInsets.all(1.5.r), // Border thickness + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22.r), + ), + padding: EdgeInsets.all(20.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.aiAnalysis.tr().toText18(weight: FontWeight.w700, color: AppColors.blackColor), + Container( + padding: EdgeInsets.all(8.r), + decoration: BoxDecoration(gradient: AppColors.aiLinearGradient, borderRadius: BorderRadius.circular(8.r)), + child: SvgPicture.asset(AppAssets.aiOverView, colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), height: 18.r, width: 18.r), + ), + ], + ), + SizedBox(height: 16.h), + if (data.summary != null && data.summary!.isNotEmpty) ...[ + LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 8.h), + data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.aiPredictions != null && data.aiPredictions!.isNotEmpty) ...[ + LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.aiPredictions!.map((item) => _buildBulletItem(item)), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.prevention != null && data.prevention!.isNotEmpty) ...[ + LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.prevention!.map((item) => _buildBulletItem(item)), + SizedBox(height: 20.h), + ], + Row( + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w), + child: Container( + width: 5.r, + height: 5.r, + decoration: const BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle), + ), + ), + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 29e15ee..c67c39d 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,45 +1,72 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; -import 'package:provider/provider.dart' show Consumer, Provider, ReadContext; +import 'package:provider/provider.dart' show Consumer, Provider, ReadContext, Selector; -import '../../../widgets/common_bottom_sheet.dart' - show showCommonBottomSheetWithoutHeight; +import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; class LabResultDetails extends StatelessWidget { final LabResult recentLabResult; final String? testDescription; - const LabResultDetails( - {super.key, - required this.recentLabResult, - required this.testDescription}); + const LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); @override Widget build(BuildContext context) { + LabViewModel labViewModel = Provider.of(context, listen: false); + final appState = getIt.get(); return CollapsingListView( title: LocaleKeys.labResultDetails.tr(context: context), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 16.h, children: [ LabNameAndStatus(context), getLabDescription(context), - LabGraph(context) + LabGraph(context), + Selector( + selector: (_, model) => model.labOrderResponseByAi, + builder: (_, aiData, __) { + if (aiData != null) { + return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); + } + return const SizedBox.shrink(); + }, + ), ], ).paddingAll(24.h), ), @@ -47,11 +74,7 @@ class LabResultDetails extends StatelessWidget { } Widget LabNameAndStatus(BuildContext context) => Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -107,7 +130,9 @@ class LabResultDetails extends StatelessWidget { ), letterSpacing: -2), ), - SizedBox(width: 4.h,), + SizedBox( + width: 4.h, + ), ], ), ), @@ -150,9 +175,7 @@ class LabResultDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - labmodel.isGraphVisible - ? LocaleKeys.historyFlowchart.tr(context: context) - : LocaleKeys.history.tr(context: context), + labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -164,22 +187,12 @@ class LabResultDetails extends StatelessWidget { spacing: 16.h, children: [ //todo handle when the graph icon is being displayed - Utils.buildSvgWithAssets( - icon: labmodel.isGraphVisible - ? AppAssets.ic_list - : AppAssets.ic_graph, - width: 24.h, - height: 24.h) - .onPress(() { + Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() { if (labmodel.shouldShowGraph) { labmodel.alterGraphVisibility(); } }), - Utils.buildSvgWithAssets( - icon: AppAssets.ic_date_filter, - width: 24, - height: 24) - .onPress(() { + Utils.buildSvgWithAssets(icon: AppAssets.ic_date_filter, width: 24, height: 24).onPress(() { showCommonBottomSheetWithoutHeight( title: LocaleKeys.setTheDateRange.tr(context: context), context, @@ -218,21 +231,17 @@ class LabResultDetails extends StatelessWidget { Widget buildBottomLabel(String label) { return Padding( - padding: const EdgeInsets.only(top:8.0), + padding: const EdgeInsets.only(top: 8.0), child: Text( label, - style: TextStyle( - fontSize: 8.f, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - color: AppColors.labelTextColor), + style: TextStyle(fontSize: 8.f, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor), ), ); } Widget historyBody(LabViewModel labmodel) { if (labmodel.isGraphVisible && labmodel.shouldShowGraph) { - var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); + var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag ?? "N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, makeGraphBasedOnActualValue: true, @@ -242,11 +251,11 @@ class LabResultDetails extends StatelessWidget { // maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, maxY: (labmodel.maxY), minY: labmodel.minY, - maxX: labmodel.filteredGraphValues.length.toDouble()-.75, + maxX: labmodel.filteredGraphValues.length.toDouble() - .75, horizontalInterval: .1, - getDrawingHorizontalLine: (value){ + getDrawingHorizontalLine: (value) { value = double.parse(value.toStringAsFixed(1)); - if(value == labmodel.highRefrenceValue ||value== labmodel.lowRefenceValue) { + if (value == labmodel.highRefrenceValue || value == labmodel.lowRefenceValue) { return FlLine( color: AppColors.bgGreenColor.withOpacity(0.6), strokeWidth: 1, @@ -256,7 +265,8 @@ class LabResultDetails extends StatelessWidget { return FlLine( color: Colors.transparent, strokeWidth: 1, - );; + ); + ; }, leftLabelFormatter: (value) { value = double.parse(value.toStringAsFixed(1)); @@ -272,11 +282,11 @@ class LabResultDetails extends StatelessWidget { return SizedBox.shrink(); // } }, - graphColor:AppColors.blackColor, + graphColor: AppColors.blackColor, graphShadowColor: Colors.transparent, graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { - if(data.isEmpty) return SizedBox.shrink(); + if (data.isEmpty) return SizedBox.shrink(); if (value == 0) { return buildBottomLabel(data[value.toInt()].label); } @@ -288,34 +298,34 @@ class LabResultDetails extends StatelessWidget { } return SizedBox.shrink(); }, - rangeAnnotations: RangeAnnotations( - horizontalRangeAnnotations: _buildRangeShades(labmodel) - ), - minX:(labmodel.filteredGraphValues.length == 1)?null : -.2, + rangeAnnotations: RangeAnnotations(horizontalRangeAnnotations: _buildRangeShades(labmodel)), + minX: (labmodel.filteredGraphValues.length == 1) ? null : -.2, scrollDirection: Axis.horizontal, height: 180.h); - }else { + } else { return labHistoryList(labmodel); } } - List _buildRangeShades( LabViewModel model,) { + List _buildRangeShades( + LabViewModel model, + ) { List ranges = []; - ranges.add(HorizontalRangeAnnotation( - y1:model.minY, - y2: model.lowRefenceValue, - color: AppColors.highAndLow.withOpacity(0.05), - )); + ranges.add(HorizontalRangeAnnotation( + y1: model.minY, + y2: model.lowRefenceValue, + color: AppColors.highAndLow.withOpacity(0.05), + )); ranges.add(HorizontalRangeAnnotation( - y1:model.lowRefenceValue, + y1: model.lowRefenceValue, y2: model.highRefrenceValue, color: AppColors.bgGreenColor.withOpacity(0.05), )); ranges.add(HorizontalRangeAnnotation( - y1:model.highRefrenceValue, + y1: model.highRefrenceValue, y2: model.maxY, color: AppColors.criticalLowAndHigh.withOpacity(0.05), )); @@ -324,11 +334,12 @@ class LabResultDetails extends StatelessWidget { Widget labHistoryList(LabViewModel labmodel) { return SizedBox( - height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, + height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : 180.h, child: ListView.separated( padding: EdgeInsets.zero, - itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ - var data = labmodel.filteredGraphValues.reversed.toList()[index]; + itemCount: labmodel.filteredGraphValues.length, + itemBuilder: (context, index) { + var data = labmodel.filteredGraphValues.reversed.toList()[index]; return LabHistoryItem( dayNameAndDate: labmodel.getFormattedDate(data.time), result: data.actualValue, @@ -368,11 +379,8 @@ class LabResultDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - LocaleKeys.whatIsThisResult.tr(context: context) - .toText16(weight: FontWeight.w600, color: AppColors.textColor), - testDescription?.toText12( - fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? - SizedBox.shrink() + LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), + testDescription?.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? SizedBox.shrink() ], )); } diff --git a/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart new file mode 100644 index 0000000..5a5aed7 --- /dev/null +++ b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart @@ -0,0 +1,538 @@ +import 'dart:convert'; + +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/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/generated/locale_keys.g.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/expandable_list_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; + +class LabAiAnalysisDetailedPage extends StatefulWidget { + const LabAiAnalysisDetailedPage({super.key}); + + @override + State createState() => _LabAiAnalysisDetailedPageState(); +} + +class _LabAiAnalysisDetailedPageState extends State { + late LabViewModel labProvider; + + @override + Widget build(BuildContext context) { + labProvider = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.aiAnalysis.tr(context: context), + child: Consumer( + builder: (context, model, child) { + if (model.labOrdersResponseByAi == null || model.labOrdersResponseByAi?.analysis == null) { + return Utils.getNoDataWidget(context); + } + + final analysis = model.labOrdersResponseByAi!.analysis!; + List expandableItems = []; + + // Summary Section + if (analysis.summary != null && analysis.summary!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + initiallyExpanded: true, + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF0B85F7))), + padding: EdgeInsets.all(16.r), + child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Generated Attributes Section (Lab Test Results) + if (analysis.aiGeneratedAttributes != null && analysis.aiGeneratedAttributes!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.aiGeneratedAttributes!.map((attr) { + return Column( + children: [ + _buildCombinedLabCard(attr), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // AI Predictions Section + if (analysis.aiPredictions != null && analysis.aiPredictions!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFFAF15))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiPredictions!.map((prediction) => _buildBulletItem(prediction)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Recommendations Section + if (analysis.aiRecommendations != null && analysis.aiRecommendations!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.suggestions.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiRecommendations!.asMap().entries.map((entry) { + final rec = entry.value; + final isLast = entry.key == analysis.aiRecommendations!.length - 1; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (rec.priority != null) ...[ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getPriorityColor(rec.priority), borderRadius: BorderRadius.circular(4.r)), + child: rec.priority!.toText12(fontWeight: FontWeight.w600, color: _getPriorityTextColor(rec.priority)), + ), + SizedBox(height: 8.h), + ], + (rec.recommendation ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + if (!isLast) ...[ + SizedBox(height: 12.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 12.h), + ], + ], + ); + }).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Risk Factors Section + if (analysis.riskFactors != null && analysis.riskFactors!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.riskFactors!.asMap().entries.map((entry) { + final risk = entry.value; + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: Color(0xFFFBCB6E).withOpacity(0.10), borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration(color: Color(0xFFFBCB6E).withAlpha(38), borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.risk ?? "").toText10(weight: FontWeight.w500, color: AppColors.blackColor), + ), + ), + ), + ], + ), + if (risk.risk != null && risk.risk!.isNotEmpty) ...[ + SizedBox(height: 4.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.guradIcon), + Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.factor ?? "").toText16(weight: FontWeight.w700, color: AppColors.blackColor), + ), + ], + ), + SizedBox(height: 4.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + if (risk.description != null && risk.description!.isNotEmpty) ...[ + SizedBox(height: 8.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + ], + ), + ), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // Prevention Section + if (analysis.prevention != null && analysis.prevention!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF18C273))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.prevention!.map((prevention) => _buildBulletItem(prevention)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // // Suggested Doctors Section + // if (analysis.suggestedDoctors != null && analysis.suggestedDoctors!.isNotEmpty) { + // expandableItems.add( + // ExpandableListItem( + // title: "Suggested Doctors".toText18(weight: FontWeight.w700, color: AppColors.blackColor), + // children: [ + // SizedBox(height: 10.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 12.r, + // side: BorderSide(width: 1, color: Color(0xFF3498DB)), + // ), + // padding: EdgeInsets.all(16.r), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: analysis.suggestedDoctors!.asMap().entries.map((entry) { + // final doctor = entry.value; + // final isLast = entry.key == analysis.suggestedDoctors!.length - 1; + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // children: [ + // Container( + // width: 40.r, + // height: 40.r, + // decoration: BoxDecoration( + // gradient: AppColors.aiLinearGradient, + // shape: BoxShape.circle, + // ), + // child: Icon( + // Icons.person, + // color: Colors.white, + // size: 24.r, + // ), + // ), + // SizedBox(width: 12.w), + // Expanded( + // child: doctor.toString().toText14( + // color: AppColors.textColorLight, + // height: 1.5, + // weight: FontWeight.w600, + // ), + // ), + // ], + // ), + // if (!isLast) ...[ + // SizedBox(height: 12.h), + // const Divider(color: AppColors.dividerColor, thickness: 1), + // SizedBox(height: 12.h), + // ], + // ], + // ); + // }).toList(), + // ), + // ), + // SizedBox(height: 8.h), + // ], + // ), + // ); + // } + + return Column( + children: [ + if (expandableItems.isEmpty) + Utils.getNoDataWidget(context) + else + CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: Color(0xff2b353e1a), + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: expandableItems, + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), + ), + ).paddingSymmetrical(16.w, 0.0), + ], + ); + }, + ), + ); + } + + Color _getPriorityColor(String? priority) { + if (priority == null) return AppColors.primaryRedColor; + switch (priority.toLowerCase()) { + case 'high': + return Color(0xFFED1C2B).withOpacity(.10); + case 'medium' || 'moderate': + return AppColors.lightGreenButtonColor; + case 'low': + return AppColors.warningColorYellow.withOpacity(.3); + default: + return AppColors.primaryRedColor; + } + } + + Color _getPriorityTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toLowerCase()) { + case 'high': + return AppColors.primaryRedColor; + case 'medium' || 'moderate': + return AppColors.lightGreenColor; + case 'low': + return AppColors.alertColor; + default: + return AppColors.primaryRedColor; + } + } + + + + Color _getResultFlagColor(String? flag) { + if (flag == null || flag.isEmpty) return Colors.grey; + switch (flag.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.lightRedButtonColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipColorSeekMedicalAdvice; + case 'N': // Normal + return AppColors.lightGreenButtonColor; + case 'A': // Abnormal + return Colors.deepOrange; + default: + return AppColors.greyLightColor; + } + } + + Color _getResultTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.primaryRedColor; + case 'N': + return AppColors.lightGreenColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipTextColorSeekMedicalAdvice; + default: + return AppColors.textColor; + } + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } + + Widget _buildCombinedLabCard(AiGeneratedAttribute attr) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2.h, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "-") ...[ + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.informationIcon, width: 14.w, height: 14.h), + SizedBox(width: 4.w), + LocaleKeys.contextDependent.tr().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ], + ), + ), + ], + ), + ] else ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + (attr.calculatedResultFlag ?? "").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ], + ), + ), + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "H") + Utils.buildSvgWithAssets(icon: AppAssets.highCautionIcon, iconColor: AppColors.primaryRedColor, width: 24.w, height: 24.h), + ], + ), + ], + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.description ?? "").toText16(isBold: true, color: _getResultTextColor(attr.calculatedResultFlag))), + + // if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty) + // Column( + // spacing: 2.h, + // children: [ + // Consumer( + // builder: (context, labViewModel, child) { + // return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColor); + // }, + // ), + // Consumer( + // builder: (context, labViewModel, child) { + // return Utils.buildSvgWithAssets( + // icon: AppAssets.lab_result_indicator, + // width: 21, + // height: 23, + // iconColor: labViewModel.getColor(attr.calculatedResultFlag ?? ""), + // ); + // }, + // ), + // ], + // ), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.testShortDescription ?? "").toText12(color: AppColors.textColor)), + ], + ), + SizedBox(height: 4.h) + ], + ), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.calculatedValue.tr()}: ${attr.resultValue!}").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ), + SizedBox(width: 8.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.unit.tr()}: ${attr.uom!}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ], + ), + SizedBox( + height: 4.h, + ), + if (attr.referanceRange != null && attr.referanceRange!.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.referenceRange.tr(context: context)}: ${attr.referanceRange!.trim()}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + if (attr.calculationFormula != null && attr.calculationFormula!.isNotEmpty) ...[ + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.calendar, iconColor: Colors.grey), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.calculationFormula.tr().toText16(weight: FontWeight.w600), + (attr.calculationFormula ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ], + ), + ), + ], + ), + ], + ], + ), + ); + } +} diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index b185d89..502afac 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -23,7 +23,7 @@ abstract class DialogService { Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}); - Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}); Future showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); @@ -114,13 +114,20 @@ class DialogServiceImp implements DialogService { } @override - Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( context, title: label ?? "", - child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), + child: exceptionBottomSheetWidget( + context: context, + message: message, + okLabel: okLabel, + cancelLabel: cancelLabel, + onOkPressed: onOkPressed, + onCancelPressed: onCancelPressed, + ), callBackFunc: () {}, ); } @@ -228,7 +235,7 @@ class DialogServiceImp implements DialogService { } } -Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) { +Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) { return Column( children: [ (message).toText16(isBold: false, color: AppColors.textColor), @@ -239,7 +246,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(context: context), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: () { Navigator.of(context).pop(); }, @@ -253,7 +260,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin SizedBox(width: 10.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(context: context), + text: okLabel ?? LocaleKeys.confirm.tr(context: context), onPressed: onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, @@ -267,7 +274,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.cancel.tr(context: context), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: (onCancelPressed == null) ? () { context.pop(); diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index d56178b..f80085a 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -113,4 +113,6 @@ class AppColors { static const Color covid29Color = Color(0xff2563EB); // #2563EB static const Color lightGreyTextColor = Color(0xFF959595); static const Color labelColorYellow = Color(0xFFFBCB6E); + + static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight); } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 575de03..aaaaa51 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -19,6 +19,7 @@ class CollapsingListView extends StatelessWidget { VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; @@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget { this.bottomChild, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, @@ -85,9 +87,11 @@ class CollapsingListView extends StatelessWidget { history: history, instructions: instructions, requests: requests, - sendEmail: sendEmail, doctorResponse: doctorResponse, + sendEmail: sendEmail, + doctorResponse: doctorResponse, bottomChild: bottomChild, trailing: trailing, + aiOverview: aiOverview, ), // : null, leading: Visibility( @@ -190,6 +194,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; @@ -206,6 +211,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.search, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, @@ -288,6 +294,7 @@ class _ScrollAnimatedTitleState extends State { if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), + if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!), if (widget.trailing != null) widget.trailing!, ] ], @@ -295,40 +302,77 @@ class _ScrollAnimatedTitleState extends State { ); } - Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { - return AnimatedSize( - duration: Duration(milliseconds: 150), - child: Container( - height: 40.h, - padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.secondaryLightRedColor, - borderRadius: 10.r, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - spacing: 8.h, - children: [ - Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), - if (t >= .5) - Text( - title, - style: context.dynamicTextStyle( - color: AppColors.primaryRedColor, - letterSpacing: -0.4, - fontSize: (12 - (2 * (1 - t))).f, - fontWeight: FontWeight.lerp( - FontWeight.w400, - FontWeight.w500, - t, - )!, + Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) { + return isAiButton + ? AnimatedSize( + duration: Duration(milliseconds: 150), + child: Center( + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 10.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), + child: ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (12 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), ), ), - ], - ), - ), - ); + ), + ) + : AnimatedSize( + duration: Duration(milliseconds: 150), + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.secondaryLightRedColor, + borderRadius: 10.r, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + if (t >= .5) + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (12 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), + ), + ); } } From 68f5e64557268751bfb8fb02c70b0170aa641e92 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 9 Feb 2026 11:16:29 +0300 Subject: [PATCH 4/6] updates --- lib/extensions/string_extensions.dart | 4 ++-- .../authentication/saved_login_screen.dart | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a236de5..6a00942 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -222,8 +222,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText16( - {Color? color, + Widget toText16({bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, @@ -247,6 +246,7 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: decorationColor), ); diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index f0fd8cb..dc6257b 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class SavedLogin extends StatefulWidget { const SavedLogin({super.key}); @@ -93,14 +94,15 @@ class _SavedLogin extends State { ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), - appState.getSelectDeviceByImeiRespModelElement != null - ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate( - DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), - "d MMMM, y 'at' HH:mm") - : '--') - .toText16(isBold: true, color: AppColors.textColor) - : SizedBox(), + Directionality( + textDirection: ui.TextDirection.ltr, + child: appState.getSelectDeviceByImeiRespModelElement != null + ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") + : '--') + .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : SizedBox(), + ), appState.getSelectDeviceByImeiRespModelElement != null ? Container( From 01c21377aabda83e2a09d6927d4bfc023f2ca1d2 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 9 Feb 2026 15:58:10 +0300 Subject: [PATCH 5/6] Updates & fixes --- assets/langs/ar-SA.json | 12 +-- assets/langs/en-US.json | 13 ++-- lib/core/api/api_client.dart | 14 +++- lib/core/api_consts.dart | 3 +- lib/core/utils/loading_utils.dart | 2 +- lib/extensions/string_extensions.dart | 3 +- lib/features/lab/lab_view_model.dart | 13 +++- .../prescriptions/prescriptions_repo.dart | 4 +- .../prescriptions_view_model.dart | 10 +-- lib/generated/locale_keys.g.dart | 5 +- .../appointment_payment_page.dart | 2 + .../widgets/appointment_card.dart | 2 +- .../LabResultByClinic.dart | 2 +- .../ai_analysis_widget.dart | 1 + .../lab_order_result_item.dart | 78 ++++++++++--------- .../lab/lab_results/lab_result_details.dart | 2 +- .../lab_ai_analysis_detailed_page.dart | 6 +- .../prescriptions/prescription_item_view.dart | 40 +++++----- lib/widgets/app_language_change.dart | 28 +++++++ lib/widgets/appbar/collapsing_list_view.dart | 4 +- lib/widgets/language_switcher.dart | 5 +- lib/widgets/loader/bottomsheet_loader.dart | 4 +- 22 files changed, 153 insertions(+), 100 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 50b0c0c..11ab941 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1528,7 +1528,7 @@ "prediction": "تنبؤ", "prevention": "وقاية", "disclaimer": "تنصل", - "thisAboveInfo": "المعلومات المذكورة أعلاه تم إنشاؤها بواسطة AI", + "thisAboveInfo": "المعلومات المذكورة أعلاه مُولّدة بواسطة AI بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.", "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", "consent": "موافقة", "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", @@ -1559,10 +1559,7 @@ "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", "contextDependent": "يعتمد السياق", "calculatedValue": "القيمة المحسوبة", - "calculationFormula": "صيغة الحساب" - - - + "calculationFormula": "صيغة الحساب", "confirmYourLocation": "قم بتأكيد موقعك", "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", "insideHospital": "أنا داخل المستشفى", @@ -1571,5 +1568,8 @@ "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", "serviceName": "اسم الخدمة", - "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً." + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", + "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", + "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "loadingAIAnalysis": "جارٍ تحميل بياناتك وتحليلها، يُرجى التحلي بالصبر ودع الذكاء الاصطناعي يقوم بالمهمة. يُرجى التحلي بالصبر، فقد يستغرق هذا بعض الوقت." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 65b08f2..3f1bc78 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1439,7 +1439,6 @@ "healthy": "Healthy", "warning": "Warning", "vitalSignDetails": "Vital Sign Details", - "resultOf": "Result of {date}", "resultOfNoDate": "Result of --", "referenceRangeBetween": "Reference range: {low} – {high} {unit}", "referenceRangeMin": "Reference range: ≥ {low} {unit}", @@ -1522,7 +1521,7 @@ "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", "serviceName": "Service Name", - "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later." + "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.", "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", "unableToSendOTP": "Unable to send Verification OTP", "aiOverView": "AI Overview", @@ -1531,8 +1530,8 @@ "prediction": "Prediction", "prevention": "Prevention", "disclaimer": "Disclaimer", - "thisAboveInfo": "The above information is generated by", - "aiDisclaimer": "Your lab result data will be securely shared with our AI service for analysis. This helps provide personalized health insights. Do you want to proceed?", + "thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.", + "aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", "consent": "Consent", "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", "bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.", @@ -1562,8 +1561,6 @@ "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", "contextDependent": "Context Dependent", "calculatedValue": "Calculated Value", - "calculationFormula": "Calculation Formula" - - - + "calculationFormula": "Calculation Formula", + "loadingAIAnalysis": "Loading and analysing your data, Please be patient and let the AI do the magic. Please be patient, This might take some time." } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index ea8062d..f74ff4d 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -181,8 +181,18 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4768663; + if (url.contains("HMGAI_Lab_Analyze_Orders_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + body['TokenID'] = "@dm!n"; + } + + if (url.contains("HMGAI_Lab_Analyzer_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + body['TokenID'] = "@dm!n"; + } + + body['TokenID'] = "@dm!n"; + body['PatientID'] = 4773882; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index c08cdda..f78c020 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -908,7 +908,8 @@ class ApiConsts { static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // ************ static values for Api **************** - static final double appVersionID = 19.7; + // static final double appVersionID = 19.7; + static final double appVersionID = 50.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/core/utils/loading_utils.dart b/lib/core/utils/loading_utils.dart index 1a99278..bd24d30 100644 --- a/lib/core/utils/loading_utils.dart +++ b/lib/core/utils/loading_utils.dart @@ -38,7 +38,7 @@ class LoadingUtils { child: Material( child: Center( child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ), ); }).then((value) { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 6a00942..f087f74 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -423,11 +423,12 @@ extension DynamicTextStyleExtension on BuildContext { Locale? locale, TextBaseline? textBaseline, FontStyle? fontStyle, + String? fontFamily, bool isLanguageSwitcher = false}) { AppState appState = getIt.get(); final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'; return TextStyle( - fontFamily: family, + fontFamily: fontFamily ?? family, fontSize: fontSize, fontWeight: fontWeight, color: color, diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 04c945d..22e4859 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -400,6 +400,10 @@ class LabViewModel extends ChangeNotifier { lowRefenceValue = minY; } + if (minY > lowRefenceValue) { + minY = lowRefenceValue - 25; + } + this.flagForHighReferenceRange = flagForHighReferenceRange; this.flagForLowReferenceRange = flagForLowReferenceRange; highTransformedReferenceValue = double.parse(transformValueInRange(highRefrenceValue, flagForHighReferenceRange ?? "").toStringAsFixed(1)); @@ -728,8 +732,9 @@ class LabViewModel extends ChangeNotifier { notifyListeners(); } - Future getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder}) async { - LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic"); + Future getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder, required String loadingText}) async { + // LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic. \nPlease be patient, This might take some time."); + LoadingUtils.showFullScreenLoader(loadingText: loadingText); List> results = []; Map orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []}; List> testResults = []; @@ -769,8 +774,8 @@ class LabViewModel extends ChangeNotifier { LoadingUtils.hideFullScreenLoader(); } - Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult}) async { - LoaderBottomSheet.showLoader(); + Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async { + LoaderBottomSheet.showLoader(loadingText: loadingText); List> results = []; results.add({ "Description": recentLabResult.description ?? '', diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 2eb5d43..dcc9de9 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -193,8 +193,8 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { "ProjectID": prescriptionsResponseModel.projectID, "DischargeNo": prescriptionsResponseModel.dischargeNo, "IsDownload": true, - // "To": Utils.appState.getAuthenticatedUser()!.emailAddress, - "To": "amjad.haroon@cloudsolutions.com.sa", + "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + // "To": "amjad.haroon@cloudsolutions.com.sa", "ClinicName": prescriptionsResponseModel.clinicDescription, "DoctorName": prescriptionsResponseModel.doctorName, }; diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index a7cd42f..319e5fc 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -54,7 +54,7 @@ class PrescriptionsViewModel extends ChangeNotifier { PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); initPrescriptionsViewModel() { - if (isPrescriptionsDataNeedsReloading) { + // if (isPrescriptionsDataNeedsReloading) { patientPrescriptionOrders.clear(); patientPrescriptionOrdersByClinic.clear(); patientPrescriptionOrdersByHospital.clear(); @@ -62,7 +62,7 @@ class PrescriptionsViewModel extends ChangeNotifier { prescriptionsOrderList.clear(); isPrescriptionsOrdersLoading = true; getPatientPrescriptionOrders(); - } + // } isSortByClinic = true; isPrescriptionsDeliveryOrdersLoading = true; notifyListeners(); @@ -102,9 +102,9 @@ class PrescriptionsViewModel extends ChangeNotifier { } Future getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isPrescriptionsDataNeedsReloading) { - return; - } + // if (!isPrescriptionsDataNeedsReloading) { + // return; + // } final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7229726..7bafadf 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1513,8 +1513,6 @@ abstract class LocaleKeys { static const trackerAndOthers = 'trackerAndOthers'; static const bmi = 'bmi'; static const yesPleaseINeedHelp = 'yesPleaseINeedHelp'; - static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; - static const unableToSendOTP = 'unableToSendOTP'; static const aiOverView = 'aiOverView'; static const generateAiAnalysis = 'generateAiAnalysis'; static const aiAnalysis = 'aiAnalysis'; @@ -1562,5 +1560,8 @@ abstract class LocaleKeys { static const servicePriceListRights = 'servicePriceListRights'; static const serviceName = 'serviceName'; static const noFreeSlot = 'noFreeSlot'; + static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; + static const unableToSendOTP = 'unableToSendOTP'; + static const loadingAIAnalysis = 'loadingAIAnalysis'; } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 097efaf..e70ddf4 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -491,6 +491,7 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); @@ -511,6 +512,7 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 49aad51..971e9ca 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -193,7 +193,7 @@ class AppointmentCard extends StatelessWidget { ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" .toText10(isEnglishOnly: true), - ), + ).toShimmer2(isShow: isLoading), ), // AppCustomChipWidget( diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index 954c09b..8fb3090 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -50,7 +50,7 @@ class LabResultByClinic extends StatelessWidget { cancelLabel: LocaleKeys.rejectView.tr(), onOkPressed: () { context.pop(); - labViewModel.getAiOverviewLabOrders(labOrder: labOrder); + labViewModel.getAiOverviewLabOrders(labOrder: labOrder, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); }, onCancelPressed: () { context.pop(); diff --git a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart index a2d0ff6..f0c9b26 100644 --- a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart +++ b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart @@ -62,6 +62,7 @@ class AiAnalysisWidget extends StatelessWidget { SizedBox(height: 20.h), ], Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), SizedBox(width: 8.w), diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index e332066..ba97731 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class LabOrderResultItem extends StatelessWidget { final VoidCallback onTap; @@ -43,49 +44,52 @@ class LabOrderResultItem extends StatelessWidget { ), (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 12.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Text( - "${tests?.resultValue} ${tests?.uOM}", - style: TextStyle( - fontSize: 24.f, - fontWeight: FontWeight.w600, - fontFamily: 'Poppins', - color: tests!.checkIfGraphShouldBeDisplayed() - ? context.read().getColor( - tests?.calculatedResultFlag ?? "", - ) - : Colors.grey.shade700, - letterSpacing: -2, - ), - ), - ), - SizedBox(width: 4.h,), - Visibility( - // visible: tests?.checkIfGraphShouldBeDisplayed() == true, - visible: true, - child: Expanded( - flex: 2, - child: Visibility( - visible: tests?.referanceRange != null, + Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( child: Text( - "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", + "${tests?.resultValue} ${tests?.uOM}", style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w500, + fontSize: 24.f, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: AppColors.greyTextColor, + color: tests!.checkIfGraphShouldBeDisplayed() + ? context.read().getColor( + tests?.calculatedResultFlag ?? "", + ) + : Colors.grey.shade700, + letterSpacing: -2, ), - // overflow: TextOverflow.ellipsis, - // maxLines: 2, - softWrap: true, ), ), - ), - ) - ], + SizedBox(width: 4.h,), + Visibility( + // visible: tests?.checkIfGraphShouldBeDisplayed() == true, + visible: true, + child: Expanded( + flex: 2, + child: Visibility( + visible: tests?.referanceRange != null, + child: Text( + "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.greyTextColor, + ), + // overflow: TextOverflow.ellipsis, + // maxLines: 2, + softWrap: true, + ), + ), + ), + ) + ], + ), ), SizedBox(height: 12.h), diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index c67c39d..cab20ae 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -45,7 +45,7 @@ class LabResultDetails extends StatelessWidget { cancelLabel: LocaleKeys.rejectView.tr(), onOkPressed: () { context.pop(); - labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult); + labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); }, onCancelPressed: () { context.pop(); diff --git a/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart index 5a5aed7..5d4fe4b 100644 --- a/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart +++ b/lib/presentation/labAiAnalysis/lab_ai_analysis_detailed_page.dart @@ -341,7 +341,7 @@ class _LabAiAnalysisDetailedPageState extends State { case 'high': return AppColors.primaryRedColor; case 'medium' || 'moderate': - return AppColors.lightGreenColor; + return AppColors.textGreenColor; case 'low': return AppColors.alertColor; default: @@ -361,7 +361,7 @@ class _LabAiAnalysisDetailedPageState extends State { case 'L': // Low case 'LL': // Very Low case 'CRITICAL LOW': - return AppColors.chipColorSeekMedicalAdvice; + return AppColors.chipColorSeekMedicalAdvice.withAlpha(75); case 'N': // Normal return AppColors.lightGreenButtonColor; case 'A': // Abnormal @@ -379,7 +379,7 @@ class _LabAiAnalysisDetailedPageState extends State { case 'CRITICAL HIGH': return AppColors.primaryRedColor; case 'N': - return AppColors.lightGreenColor; + return AppColors.textGreenColor; case 'L': // Low case 'LL': // Very Low case 'CRITICAL LOW': diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index a41b53f..03a343f 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -155,26 +155,26 @@ class PrescriptionItemView extends StatelessWidget { ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 14.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.checkAvailability.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).toShimmer2(isShow: isLoading), - ), - // SizedBox(width: 16.h), - ], - ).paddingSymmetrical(16.h, 16.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.checkAvailability.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + // borderColor: AppColors.primaryRedColor.withOpacity(0.0), + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ).toShimmer2(isShow: isLoading), + // ), + // // SizedBox(width: 16.h), + // ], + // ).paddingSymmetrical(16.h, 16.h), ], ), ); diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index c94195a..e75ff6f 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -4,9 +4,14 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_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/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; class AppLanguageChange extends StatefulWidget { AppLanguageChange({Key? key}) : super(key: key); @@ -20,6 +25,11 @@ class AppLanguageChange extends StatefulWidget { class _AppLanguageChangeState extends State { String? selectedValue; + late MyAppointmentsViewModel myAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; + late TodoSectionViewModel todoSectionViewModel; + @override void initState() { super.initState(); @@ -33,6 +43,12 @@ class _AppLanguageChangeState extends State { @override Widget build(BuildContext context) { selectedValue ??= context.locale.languageCode; + + myAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); + todoSectionViewModel = Provider.of(context, listen: false); + return Column( spacing: 24.h, children: [ @@ -51,6 +67,18 @@ class _AppLanguageChangeState extends State { text: LocaleKeys.save.tr(context: context), onPressed: () { context.setLocale(selectedValue == 'en' ? Locale('en', 'US') : Locale('ar', 'SA')).then((val) { + //Reload My Appointments Data + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + + //Reload Immediate LiveCare Data + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); + + //Reload Emergency Services Data + emergencyServicesViewModel.checkPatientERAdvanceBalance(); + Navigator.pop(context); }); }), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index aaaaa51..2522196 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -310,7 +310,7 @@ class _ScrollAnimatedTitleState extends State { child: Container( height: 40.h, padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 10.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), child: ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), @@ -326,7 +326,7 @@ class _ScrollAnimatedTitleState extends State { style: context.dynamicTextStyle( color: AppColors.primaryRedColor, letterSpacing: -0.4, - fontSize: (12 - (2 * (1 - t))).f, + fontSize: (14 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( FontWeight.w400, FontWeight.w500, diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 91e45b2..033eaaf 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -59,14 +59,15 @@ class _LanguageSelectorState extends State { Utils.buildSvgWithAssets(icon: AppAssets.language), SizedBox(width: 6.h), Text( - currentLangData['name']?.toUpperCase() ?? 'EN', + // currentLangData['name']?.toUpperCase() ?? 'EN', + widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", style: context.dynamicTextStyle( fontWeight: FontWeight.w500, fontSize: 14.f, color: AppColors.primaryRedColor, letterSpacing: 0.1, isLanguageSwitcher: true, - ), + fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "GESSTwo"), ), ], ), diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index 813ad5b..71b8341 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api_consts.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/widget_extensions.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; class LoaderBottomSheet { @@ -31,7 +33,7 @@ class LoaderBottomSheet { ), child: Center( child: Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ); }, ).whenComplete(() { From 20ed54243b46353e10e270957c9d27191b9339bf Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 9 Feb 2026 17:27:44 +0300 Subject: [PATCH 6/6] updates --- lib/core/api/api_client.dart | 4 ++-- .../symptoms_checker/possible_conditions_screen.dart | 3 ++- .../symptoms_checker/symptoms_selector_screen.dart | 3 ++- lib/presentation/symptoms_checker/triage_screen.dart | 3 ++- lib/services/navigation_service.dart | 7 +++++++ 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index f74ff4d..231b037 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -191,8 +191,8 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 4773882; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4768663; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 8fb0e0f..3d288d7 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -205,7 +205,8 @@ class PossibleConditionsPage extends StatelessWidget { final SymptomsCheckerViewModel symptomsCheckerViewModel = context.read(); symptomsCheckerViewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }, ), trailing: _buildTrailingSection(context), diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index 2bfcb62..cbbfc68 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -89,7 +89,8 @@ class _SymptomsSelectorPageState extends State { context.pop(); viewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }), child: viewModel.isBodySymptomsLoading ? _buildLoadingShimmer() diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 0811833..0231ef1 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -316,7 +316,8 @@ class _TriagePageState extends State { context.pop(); viewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }, ), callBackFunc: () {}, diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index fe95140..da4a75b 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -34,6 +34,13 @@ class NavigationService { ); } + void replaceAllRoutesAndNavigateToLandingAuth() { + navigatorKey.currentState?.pushNamedAndRemoveUntil( + AppRoutes.landingScreen, + (Route route) => false, + ); + } + void pushAndReplace(String routeName) { navigatorKey.currentState?.pushReplacementNamed(routeName); }