From e8319a0d3f1b0988285c33365c04bad3ab8673aa Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 8 Oct 2025 12:26:23 +0300 Subject: [PATCH 01/46] active medication --- .../active_medication_page.dart | 682 ++++++++++++++++++ lib/presentation/home/landing_page.dart | 11 +- 2 files changed, 692 insertions(+), 1 deletion(-) create mode 100644 lib/presentation/active_medication/active_medication_page.dart diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart new file mode 100644 index 0000000..21e1a8b --- /dev/null +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -0,0 +1,682 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; + +import '../../core/app_assets.dart'; +import '../../core/utils/utils.dart'; +import '../../generated/locale_keys.g.dart'; +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; +import 'package:intl/intl.dart'; + +import '../../widgets/buttons/custom_button.dart'; +import '../../widgets/chip/app_custom_chip_widget.dart'; // for date formatting + + + +class ActiveMedicationPage extends StatefulWidget { + @override + State createState() => _ActiveMedicationPageState(); +} + +class _ActiveMedicationPageState extends State { + + late DateTime currentDate; + late DateTime selectedDate; + +// Info for each day (customizable) + final Map> dayInfo = { + 0: {"text": "Medications", "icon": Icons.medication_outlined, "description": "Affected"}, + 1: {"text": "Doctor Appointment", "icon": Icons.local_hospital_outlined, "description": "Twice"}, + 2: {"text": "Rest Day", "icon": Icons.self_improvement_outlined, "description": "Daily"}, + 3: {"text": "Gym Session", "icon": Icons.fitness_center_outlined, "description": "Affected"}, + 4: {"text": "Meeting", "icon": Icons.meeting_room_outlined, "description": "Twice"}, + 5: {"text": "Shopping", "icon": Icons.shopping_bag_outlined, "description": "Daily"}, + 6: {"text": "Family Time", "icon": Icons.family_restroom_outlined, "description": "Affected"}, + }; + + @override + void initState() { + super.initState(); + currentDate = DateTime.now(); + selectedDate = currentDate; + } + +// Generate today + next 6 days + List getUpcomingDays() { + return List.generate(7, (index) => currentDate.add(Duration(days: index))); + } + + @override + Widget build(BuildContext context) { + + List days = getUpcomingDays(); + int dayIndex = selectedDate.difference(currentDate).inDays; + + String dateText = + "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)} "; + String infoMed = dayInfo[dayIndex]?["text"] ?? "No Info"; + IconData infoImg= dayInfo[dayIndex]?["icon"] ?? Icons.info_outline; + String medDetails = dayInfo[dayIndex]?["description"] ?? "No Info"; + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + }, + onLanguageChanged: (lang) {}, + hideLogoAndLang: true, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 65, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: days.length, + itemBuilder: (context, index) { + DateTime day = days[index]; + String label = DateFormat('E').format(day); // Mon, Tue + return Padding( + padding: const EdgeInsets.only(right: 12), + child: buildDayCard(label, day), + ); + }, + ), + ), + + const SizedBox(height: 20), + +// Show full date text + Text( + dateText, + style: TextStyle( + color: AppColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w500), + ), + const Text( + "Medications", + style: TextStyle( + color: AppColors.primaryRedBorderColor,fontSize: 12, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 16), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, + hasShadow: true,), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(1), + child: Container( + width: 59, + height: 59, + decoration: BoxDecoration( + border: Border.all( + color: AppColors.spacerLineColor,// Border color + width: 1.0, ), + borderRadius: BorderRadius.circular(30),// Border width + ), + child: + Icon(infoImg, size: 26), + // Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) + ), + ), + const SizedBox(width: 12), + Text( + infoMed, + style: TextStyle( + fontSize: 16, + height: 1.2, + fontWeight: FontWeight.w700, + color: Colors.black87), + ), + ], + ), + const SizedBox(height: 12), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "Route: $medDetails", + ), + AppCustomChipWidget( + labelText: "Frequency: $medDetails", + ), + AppCustomChipWidget( + labelText: "Daily Does $medDetails", + ), + AppCustomChipWidget( + labelText: "Duration: $medDetails ", + ), + ], + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: Colors.grey,), + const SizedBox(width: 8), + Expanded( + child: Text( + "Remark: some remarks about the prescription will be here", + style: TextStyle( + fontSize: 10, + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.visible, + ), + ) + ], + ), + ], + ).paddingAll(16), + const Divider( + indent: 0, + endIndent: 0, + thickness: 1, + color: AppColors.greyColor, + ), + // Reminder Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(10),// Border width + ), + child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Set Reminder", + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, + color: AppColors.textColor), + ), + const Text( + "Notify me before the consumption time", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, + color: AppColors.textColorLight), + ), + ], + ), + ], + ), + // Switch( + // value: isActiveReminder, + // onChanged: (_) {}, + // activeColor: Colors.green, + // ), + ], + ).paddingOnly(left:16, right: 16), + const Divider( + indent: 0, + endIndent: 0, + thickness: 1, + color: AppColors.greyColor, + ), + +// Buttons + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.checkAvailability.tr(), + fontSize: 14, + onPressed: () async { + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + ), + const SizedBox(width: 12), + Expanded( + child: CustomButton( + text: LocaleKeys.readInstructions.tr(), + fontSize: 14, + onPressed: () async { + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + ), + ), + ], + ).paddingAll(16), + ], + ), + ) +// Expanded( +// child: ListView( +// children: [ +// Container( +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, +// hasShadow: true,), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// children: [ +// ClipRRect( +// borderRadius: BorderRadius.circular(1), +// child: Container( +// width: 59, +// height: 59, +// decoration: BoxDecoration( +// border: Border.all( +// color: AppColors.spacerLineColor,// Border color +// width: 1.0, ), +// borderRadius: BorderRadius.circular(30),// Border width +// ), +// child: +// Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) +// ), +// ), +// const SizedBox(width: 12), +// const Expanded( +// child: Text( +// "Diclofenac Diethylamine 1% Topical Gel", +// style: TextStyle( +// fontSize: 16, +// height: 1.2, +// fontWeight: FontWeight.w700, +// color: Colors.black87), +// ), +// ), +// ], +// ), +// const SizedBox(height: 12), +// Wrap( +// direction: Axis.horizontal, +// spacing: 4.h, +// runSpacing: 4.h, +// children: [ +// AppCustomChipWidget( +// labelText: "Route: Affected Area ", +// ), +// AppCustomChipWidget( +// labelText: "Route: Affected Area ", +// ), +// AppCustomChipWidget( +// labelText: "Daily Does 2", +// ), +// AppCustomChipWidget( +// labelText: "Route: Affected Area ", +// ), +// ], +// ), +// const SizedBox(height: 12), +// Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Icon(Icons.info_outline, color: Colors.grey,), +// const SizedBox(width: 8), +// Expanded( +// child: Text( +// "Remark: some remarks about the prescription will be here", +// style: TextStyle( +// fontSize: 10, +// color: AppColors.greyTextColor, +// fontWeight: FontWeight.w500, +// ), +// overflow: TextOverflow.visible, +// ), +// ) +// ], +// ), +// ], +// ).paddingAll(16), +// const Divider( +// indent: 0, +// endIndent: 0, +// thickness: 1, +// color: AppColors.greyColor, +// ), +// // Reminder Row +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Row( +// children: [ +// Container( +// width: 40, +// height: 40, +// decoration: BoxDecoration( +// color: AppColors.greyColor, +// borderRadius: BorderRadius.circular(10),// Border width +// ), +// child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), +// const SizedBox(width: 8), +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Text( +// "Set Reminder", +// style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, +// color: AppColors.textColor), +// ), +// const Text( +// "Notify me before the consumption time", +// style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, +// color: AppColors.textColorLight), +// ), +// ], +// ), +// ], +// ), +// // Switch( +// // value: isActiveReminder, +// // onChanged: (_) {}, +// // activeColor: Colors.green, +// // ), +// ], +// ).paddingOnly(left:16, right: 16), +// const Divider( +// indent: 0, +// endIndent: 0, +// thickness: 1, +// color: AppColors.greyColor, +// ), +// +// // Buttons +// Row( +// children: [ +// Expanded( +// child: CustomButton( +// text: LocaleKeys.checkAvailability.tr(), +// fontSize: 14, +// onPressed: () async { +// }, +// backgroundColor: AppColors.secondaryLightRedColor, +// borderColor: AppColors.secondaryLightRedColor, +// textColor: AppColors.errorColor, +// ), +// ), +// const SizedBox(width: 12), +// Expanded( +// child: CustomButton( +// text: LocaleKeys.readInstructions.tr(), +// fontSize: 14, +// onPressed: () async { +// }, +// backgroundColor: AppColors.primaryRedColor, +// borderColor: AppColors.primaryRedColor, +// textColor: AppColors.whiteColor, +// ), +// ), +// ], +// ).paddingAll(16), +// ], +// ), +// ) +// // MedicationCard(), +// // SizedBox(height: 16), +// // MedicationCard(isActiveReminder: true), +// ], +// ), +// ), + ] + ), + + ), + ); + } + + Widget buildDayCard(String label, DateTime date) { + bool isSelected = selectedDate.day == date.day && + selectedDate.month == date.month && + selectedDate.year == date.year; + + return GestureDetector( + onTap: () { + setState(() { + selectedDate = date; + }); + }, + child: Container( + width: 57, + height: 65, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: isSelected ? AppColors.secondaryLightRedBorderColor: AppColors.transparent, + border: Border.all( + color: isSelected ? AppColors.primaryRedBorderColor : AppColors.spacerLineColor, + width: 1.0, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + date.day == currentDate.day ? "Today" : label, + style: TextStyle( + color: isSelected ? AppColors.primaryRedBorderColor : AppColors.greyTextColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 5), + Text( + date.day.toString(), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: isSelected ? AppColors.primaryRedBorderColor : AppColors.textColor, + ), + ), + ], + ), + ), + ), + ); + } + + String getSuffix(int day) { + if (day == 1 || day == 21 || day == 31) return "st"; + if (day == 2 || day == 22) return "nd"; + if (day == 3 || day == 23) return "rd"; + return "th"; + } +} + +class MedicationCard extends StatelessWidget { + final bool isActiveReminder; + const MedicationCard({super.key, this.isActiveReminder = false}); + + Color get primaryRed => const Color(0xFFE84B3A); + + @override + Widget build(BuildContext context) { + return + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, + hasShadow: true,), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(1), + child: Container( + width: 59, + height: 59, + decoration: BoxDecoration( + border: Border.all( + color: AppColors.spacerLineColor,// Border color + width: 1.0, ), + borderRadius: BorderRadius.circular(30),// Border width + ), + child: + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) + ), + ), + const SizedBox(width: 12), + const Expanded( + child: Text( + "Diclofenac Diethylamine 1% Topical Gel", + style: TextStyle( + fontSize: 16, + height: 1.2, + fontWeight: FontWeight.w700, + color: Colors.black87), + ), + ), + ], + ), + const SizedBox(height: 12), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "Route: Affected Area ", + ), + AppCustomChipWidget( + labelText: "Route: Affected Area ", + ), + AppCustomChipWidget( + labelText: "Daily Does 2", + ), + AppCustomChipWidget( + labelText: "Route: Affected Area ", + ), + ], + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: Colors.grey,), + const SizedBox(width: 8), + Expanded( + child: Text( + "Remark: some remarks about the prescription will be here", + style: TextStyle( + fontSize: 10, + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.visible, + ), + ) + ], + ), + ], + ).paddingAll(16), + const Divider( + indent: 0, + endIndent: 0, + thickness: 1, + color: AppColors.greyColor, + ), + // Reminder Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(10),// Border width + ), + child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Set Reminder", + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, + color: AppColors.textColor), + ), + const Text( + "Notify me before the consumption time", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, + color: AppColors.textColorLight), + ), + ], + ), + ], + ), + Switch( + value: isActiveReminder, + onChanged: (_) {}, + activeColor: Colors.green, + ), + ], + ).paddingOnly(left:16, right: 16), + const Divider( + indent: 0, + endIndent: 0, + thickness: 1, + color: AppColors.greyColor, + ), + +// Buttons + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.checkAvailability.tr(), + fontSize: 14, + onPressed: () async { + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + ), + const SizedBox(width: 12), + Expanded( + child: CustomButton( + text: LocaleKeys.readInstructions.tr(), + fontSize: 14, + onPressed: () async { + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + ), + ), + ], + ).paddingAll(16), + ], + ), + ); + } +} + diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index cb2f76f..2bb1b14 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -44,6 +44,8 @@ import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dar import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import '../active_medication/active_medication_page.dart'; + class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -379,7 +381,14 @@ class _LandingPageState extends State { "Services".toText16(isBold: true), Row( children: [ - "View all services".toText12(color: AppColors.primaryRedColor), + "View all services".toText12(color: AppColors.primaryRedColor).onPress(() { + Navigator.of(context) + .push( + CustomPageRoute( + page: ActiveMedicationPage(), + ), + ); + }), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], From de8c7bc60523fcd041507130e9de461fd239280e Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 21 Oct 2025 10:51:45 +0300 Subject: [PATCH 02/46] active medication --- lib/core/api_consts.dart | 6 +- lib/core/utils/utils.dart | 2 +- .../active_prescriptions_repo.dart | 99 +++ .../active_prescriptions_view_model.dart | 57 ++ .../active_prescriptions_response_model.dart | 149 ++++ lib/main.dart | 4 + .../active_medication_page.dart | 714 ++++++++---------- lib/presentation/home/landing_page.dart | 6 - .../medical_file/medical_file_page.dart | 13 +- lib/services/dialog_service.dart | 16 + .../reminder_timer_dialog.dart | 155 ++++ 11 files changed, 794 insertions(+), 427 deletions(-) create mode 100644 lib/features/active_prescriptions/active_prescriptions_repo.dart create mode 100644 lib/features/active_prescriptions/active_prescriptions_view_model.dart create mode 100644 lib/features/active_prescriptions/models/active_prescriptions_response_model.dart create mode 100644 lib/widgets/medication_reminder/reminder_timer_dialog.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9591cfe..8d7ae29 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -419,7 +419,7 @@ var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_Get var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; -var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; +// var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult'; var GET_CALL_REQUEST_TYPE_LOV = 'Services/Doctors.svc/REST/GetCallRequestType_LOV'; @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -838,7 +838,7 @@ class ApiConsts { static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; - + static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index c4a2db8..fe5dd94 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -643,7 +643,7 @@ class Utils { } /// Widget to build an SVG from network - static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover}) { + static Widget buildImgWithNetwork({required String url, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover}) { return Image.network( url, width: width, diff --git a/lib/features/active_prescriptions/active_prescriptions_repo.dart b/lib/features/active_prescriptions/active_prescriptions_repo.dart new file mode 100644 index 0000000..847d6e8 --- /dev/null +++ b/lib/features/active_prescriptions/active_prescriptions_repo.dart @@ -0,0 +1,99 @@ + + +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; + +import '../../core/api/api_client.dart'; +import '../../core/api_consts.dart'; +import '../../core/common_models/generic_api_model.dart'; +import '../../core/exceptions/api_failure.dart'; +import '../../services/logger_service.dart'; + +abstract class ActivePrescriptionsRepo { + + Future>> getActivePrescriptionsDetails(); + +} + +class ActivePrescriptionsRepoImp implements ActivePrescriptionsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ActivePrescriptionsRepoImp({required this.loggerService, required this.apiClient}); + + @override + + Future>> getActivePrescriptionsDetails() async + { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getActivePrescriptionsDetails, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['GetActivePrescriptionReportByPatientIDList']; + + // final prescriptionLists = list.map((item) => ActivePrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + return ['List_ActiveGetPrescriptionReportByPatientID']; + //apiResponse; + } 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())); + } + } + + + + + // + // Future> getActiveMedications() { + // try { + // GenericApiModel? apiResponse; + // Failure? failure; + // return apiClient.post( + // ApiConsts.getActivePrescriptionsDetails, + // body: patientDeviceDataRequest, + // onFailure: (error, statusCode, {messageStatus, failureType}) { + // failure = failureType; + // }, + // onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + // try { + // apiResponse = GenericApiModel( + // messageStatus: messageStatus, + // statusCode: statusCode, + // errorMessage: errorMessage, + // data: response, + // ); + // } catch (e) { + // failure = DataParsingFailure(e.toString()); + // } + // }, + // ).then((_) { + // if (failure != null) return Left(failure!); + // if (apiResponse == null) return Left(ServerFailure("Unknown error")); + // return Right(apiResponse!); + // }); + // } catch (e) { + // return Future.value(Left(UnknownFailure(e.toString()))); + // } + // } +} \ No newline at end of file diff --git a/lib/features/active_prescriptions/active_prescriptions_view_model.dart b/lib/features/active_prescriptions/active_prescriptions_view_model.dart new file mode 100644 index 0000000..d995f96 --- /dev/null +++ b/lib/features/active_prescriptions/active_prescriptions_view_model.dart @@ -0,0 +1,57 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ActivePrescriptionsViewModel extends ChangeNotifier { + bool isActivePrescriptionsDetailsLoading = false; + + late ActivePrescriptionsRepo activePrescriptionsRepo; + late ErrorHandlerService errorHandlerService; + + // Prescription Orders Lists + List activePrescriptionsDetailsList = []; + + initActivePrescriptionsViewModel() { + getActiveMedications(); + notifyListeners(); + } + + setPrescriptionsDetailsLoading() { + isActivePrescriptionsDetailsLoading = true; + // activePrescriptionsDetailsList.clear(); + notifyListeners(); + } + + Future getActiveMedications( {Function(dynamic)? onSuccess, Function(String)? onError}) + async { + final result = await activePrescriptionsRepo.getActivePrescriptionsDetails(); + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + activePrescriptionsDetailsList = apiResponse.data!; + isActivePrescriptionsDetailsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + print(activePrescriptionsDetailsList.length); + } + } + }, + ); + } + + + + + + + + + + +} diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart new file mode 100644 index 0000000..878e191 --- /dev/null +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -0,0 +1,149 @@ +import 'dart:convert'; + +class ActivePrescriptionsResponseModel { + dynamic address; + int? appointmentNo; + dynamic clinic; + dynamic companyName; + int? days; + dynamic doctorName; + int? doseDailyQuantity; + String? frequency; + int? frequencyNumber; + dynamic image; + dynamic imageExtension; + dynamic imageSrcUrl; + String? imageString; + dynamic imageThumbUrl; + dynamic isCovered; + String? itemDescription; + int? itemId; + String? orderDate; + int? patientId; + dynamic patientName; + dynamic phoneOffice1; + dynamic prescriptionQr; + int? prescriptionTimes; + dynamic productImage; + String? productImageBase64; + String? productImageString; + int? projectId; + dynamic projectName; + dynamic remarks; + String? route; + String? sku; + int? scaleOffset; + String? startDate; + + ActivePrescriptionsResponseModel({ + this.address, + this.appointmentNo, + this.clinic, + this.companyName, + this.days, + this.doctorName, + this.doseDailyQuantity, + this.frequency, + this.frequencyNumber, + this.image, + this.imageExtension, + this.imageSrcUrl, + this.imageString, + this.imageThumbUrl, + this.isCovered, + this.itemDescription, + this.itemId, + this.orderDate, + this.patientId, + this.patientName, + this.phoneOffice1, + this.prescriptionQr, + this.prescriptionTimes, + this.productImage, + this.productImageBase64, + this.productImageString, + this.projectId, + this.projectName, + this.remarks, + this.route, + this.sku, + this.scaleOffset, + this.startDate, + }); + + factory ActivePrescriptionsResponseModel.fromRawJson(String str) => ActivePrescriptionsResponseModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ActivePrescriptionsResponseModel.fromJson(Map json) => ActivePrescriptionsResponseModel( + address: json["Address"], + appointmentNo: json["AppointmentNo"], + clinic: json["Clinic"], + companyName: json["CompanyName"], + days: json["Days"], + doctorName: json["DoctorName"], + doseDailyQuantity: json["DoseDailyQuantity"], + frequency: json["Frequency"], + frequencyNumber: json["FrequencyNumber"], + image: json["Image"], + imageExtension: json["ImageExtension"], + imageSrcUrl: json["ImageSRCUrl"], + imageString: json["ImageString"], + imageThumbUrl: json["ImageThumbUrl"], + isCovered: json["IsCovered"], + itemDescription: json["ItemDescription"], + itemId: json["ItemID"], + orderDate: json["OrderDate"], + patientId: json["PatientID"], + patientName: json["PatientName"], + phoneOffice1: json["PhoneOffice1"], + prescriptionQr: json["PrescriptionQR"], + prescriptionTimes: json["PrescriptionTimes"], + productImage: json["ProductImage"], + productImageBase64: json["ProductImageBase64"], + productImageString: json["ProductImageString"], + projectId: json["ProjectID"], + projectName: json["ProjectName"], + remarks: json["Remarks"], + route: json["Route"], + sku: json["SKU"], + scaleOffset: json["ScaleOffset"], + startDate: json["StartDate"], + ); + + Map toJson() => { + "Address": address, + "AppointmentNo": appointmentNo, + "Clinic": clinic, + "CompanyName": companyName, + "Days": days, + "DoctorName": doctorName, + "DoseDailyQuantity": doseDailyQuantity, + "Frequency": frequency, + "FrequencyNumber": frequencyNumber, + "Image": image, + "ImageExtension": imageExtension, + "ImageSRCUrl": imageSrcUrl, + "ImageString": imageString, + "ImageThumbUrl": imageThumbUrl, + "IsCovered": isCovered, + "ItemDescription": itemDescription, + "ItemID": itemId, + "OrderDate": orderDate, + "PatientID": patientId, + "PatientName": patientName, + "PhoneOffice1": phoneOffice1, + "PrescriptionQR": prescriptionQr, + "PrescriptionTimes": prescriptionTimes, + "ProductImage": productImage, + "ProductImageBase64": productImageBase64, + "ProductImageString": productImageString, + "ProjectID": projectId, + "ProjectName": projectName, + "Remarks": remarks, + "Route": route, + "SKU": sku, + "ScaleOffset": scaleOffset, + "StartDate": startDate, + }; +} diff --git a/lib/main.dart b/lib/main.dart index 20507d0..cf038f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; @@ -125,6 +126,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index 21e1a8b..6394002 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -1,64 +1,72 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; - -import '../../core/app_assets.dart'; -import '../../core/utils/utils.dart'; +// import 'package:sizer/sizer.dart'; +import '../../core/dependencies.dart'; +import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; +import '../../features/active_prescriptions/models/active_prescriptions_response_model.dart'; import '../../generated/locale_keys.g.dart'; +import '../../services/dialog_service.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import 'package:intl/intl.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; // for date formatting +import 'package:provider/provider.dart'; class ActiveMedicationPage extends StatefulWidget { + //inal List activePrescriptionsResponseModel; + + ActiveMedicationPage({super.key, }); + + + + @override State createState() => _ActiveMedicationPageState(); } class _ActiveMedicationPageState extends State { - late DateTime currentDate; late DateTime selectedDate; -// Info for each day (customizable) - final Map> dayInfo = { - 0: {"text": "Medications", "icon": Icons.medication_outlined, "description": "Affected"}, - 1: {"text": "Doctor Appointment", "icon": Icons.local_hospital_outlined, "description": "Twice"}, - 2: {"text": "Rest Day", "icon": Icons.self_improvement_outlined, "description": "Daily"}, - 3: {"text": "Gym Session", "icon": Icons.fitness_center_outlined, "description": "Affected"}, - 4: {"text": "Meeting", "icon": Icons.meeting_room_outlined, "description": "Twice"}, - 5: {"text": "Shopping", "icon": Icons.shopping_bag_outlined, "description": "Daily"}, - 6: {"text": "Family Time", "icon": Icons.family_restroom_outlined, "description": "Affected"}, - }; + + ActivePrescriptionsViewModel? activePreVM; @override void initState() { + activePreVM = Provider.of(context, listen: false); + activePreVM?.getActiveMedications(); + print(activePreVM?.activePrescriptionsDetailsList); super.initState(); currentDate = DateTime.now(); selectedDate = currentDate; } + // Generate today + next 6 days List getUpcomingDays() { return List.generate(7, (index) => currentDate.add(Duration(days: index))); } + // on/off toggle + bool isOn = true; + get index => null; @override Widget build(BuildContext context) { - + // activePreVM = Provider.of(context, listen: false); List days = getUpcomingDays(); int dayIndex = selectedDate.difference(currentDate).inDays; - - String dateText = - "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)} "; - String infoMed = dayInfo[dayIndex]?["text"] ?? "No Info"; - IconData infoImg= dayInfo[dayIndex]?["icon"] ?? Icons.info_outline; - String medDetails = dayInfo[dayIndex]?["description"] ?? "No Info"; + String dateText = "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)} "; return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( @@ -74,10 +82,11 @@ class _ActiveMedicationPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - height: 65, + height: 65.h, child: ListView.builder( scrollDirection: Axis.horizontal, - itemCount: days.length, + itemCount: days.length, + // itemCount: widget.details.length, itemBuilder: (context, index) { DateTime day = days[index]; String label = DateFormat('E').format(day); // Mon, Tue @@ -88,23 +97,22 @@ class _ActiveMedicationPageState extends State { }, ), ), - - const SizedBox(height: 20), + SizedBox(height: 20.h), // Show full date text Text( dateText, style: TextStyle( color: AppColors.textColor, - fontSize: 16, + fontSize: 16.fSize, fontWeight: FontWeight.w500), ), - const Text( - "Medications", + Text( + "Medications".needTranslation, style: TextStyle( - color: AppColors.primaryRedBorderColor,fontSize: 12, fontWeight: FontWeight.w500), + color: AppColors.primaryRedBorderColor,fontSize: 12.fSize, fontWeight: FontWeight.w500), ), - const SizedBox(height: 16), + SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, hasShadow: true,), @@ -119,61 +127,64 @@ class _ActiveMedicationPageState extends State { ClipRRect( borderRadius: BorderRadius.circular(1), child: Container( - width: 59, - height: 59, + width: 59.h, + height: 59.h, decoration: BoxDecoration( border: Border.all( color: AppColors.spacerLineColor,// Border color - width: 1.0, ), + width: 1.0.h, ), borderRadius: BorderRadius.circular(30),// Border width ), child: - Icon(infoImg, size: 26), - // Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) + Utils.buildImgWithNetwork(url: activePreVM!.activePrescriptionsDetailsList[index].productImageString.toString(),width: 26.h,) ), ), - const SizedBox(width: 12), + SizedBox(width: 12.h), Text( - infoMed, + activePreVM!.activePrescriptionsDetailsList[index].itemDescription.toString(), style: TextStyle( - fontSize: 16, - height: 1.2, + fontSize: 16.fSize, + height: 1.2.h, fontWeight: FontWeight.w700, color: Colors.black87), ), ], ), - const SizedBox(height: 12), + SizedBox(height: 12.h), + activePreVM!.activePrescriptionsDetailsList.length > 0 ? Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "Route: $medDetails", + labelText: "Route: ${activePreVM?.activePrescriptionsDetailsList[index].route}", ), AppCustomChipWidget( - labelText: "Frequency: $medDetails", + labelText: "Frequency: ${activePreVM?.activePrescriptionsDetailsList[index].frequency}".needTranslation, ), AppCustomChipWidget( - labelText: "Daily Does $medDetails", + labelText: "Daily Does ${activePreVM?.activePrescriptionsDetailsList[index].doseDailyQuantity}".needTranslation, ), AppCustomChipWidget( - labelText: "Duration: $medDetails ", + labelText: "Duration: ${activePreVM?.activePrescriptionsDetailsList[index].days} ".needTranslation, ), ], + ): + Container( + child: Text("no data"), ), - const SizedBox(height: 12), + SizedBox(height: 12.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.info_outline, color: Colors.grey,), - const SizedBox(width: 8), + SizedBox(width: 8.h), Expanded( child: Text( - "Remark: some remarks about the prescription will be here", + "Remark: some remarks about the prescription will be here".needTranslation, style: TextStyle( - fontSize: 10, + fontSize: 10.fSize, color: AppColors.greyTextColor, fontWeight: FontWeight.w500, ), @@ -194,41 +205,96 @@ class _ActiveMedicationPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: AppColors.greyColor, - borderRadius: BorderRadius.circular(10),// Border width - ), - child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Set Reminder", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, - color: AppColors.textColor), - ), - const Text( - "Notify me before the consumption time", - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, - color: AppColors.textColorLight), - ), - ], + Container( + width: 40.h, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(10),// Border width ), - ], + child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor) + // MedicalFileCard( + // label: "Vaccine Info".needTranslation, + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets..bell, + // isLargeText: true, + // iconSize: 36.h, + // ) + ), + SizedBox(width: 8.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Set Reminder", + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14.fSize, + color: AppColors.textColor), + ), + Text( + "Notify me before the consumption time", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12.fSize, + color: AppColors.textColorLight), + ), + ], + ).onPress(() { + DialogService dialogService = getIt.get(); + dialogService.showReminderBottomSheetWithoutHWithChild( + label: "Set the timer for reminder".needTranslation, + message: "", + child: ReminderTimerDialog(), + onOkPressed: () {}, + ); + }), + ), + GestureDetector( + onTap: () { + setState(() { + isOn = !isOn; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 50.h, + height: 28.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: isOn ? AppColors.lightGreenColor: AppColors.greyColor, + ), + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + alignment: isOn ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(3), + child: Container( + width: 22.h, + height: 22.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isOn ? AppColors.textGreenColor : AppColors.greyTextColor, + ), + ), ), + ), + ), + ), + SizedBox(width: 2.h), // Switch( - // value: isActiveReminder, - // onChanged: (_) {}, - // activeColor: Colors.green, + // value: isOn, + // onChanged: (value){ + // setState(() { + // isOn = value; + // }); + // }, + // activeColor: AppColors.lightGreenColor, + // activeTrackColor: AppColors.lightGreenColor, + // activeThumbColor: AppColors.textGreenColor, + // inactiveThumbColor: AppColors.greyTextColor, + // inactiveTrackColor: AppColors.greyColor, // ), ], - ).paddingOnly(left:16, right: 16), + ).paddingAll(16), const Divider( indent: 0, endIndent: 0, @@ -242,7 +308,7 @@ class _ActiveMedicationPageState extends State { Expanded( child: CustomButton( text: LocaleKeys.checkAvailability.tr(), - fontSize: 14, + fontSize: 14.fSize, onPressed: () async { }, backgroundColor: AppColors.secondaryLightRedColor, @@ -250,11 +316,11 @@ class _ActiveMedicationPageState extends State { textColor: AppColors.errorColor, ), ), - const SizedBox(width: 12), + SizedBox(width: 12.h), Expanded( child: CustomButton( text: LocaleKeys.readInstructions.tr(), - fontSize: 14, + fontSize: 14.fSize, onPressed: () async { }, backgroundColor: AppColors.primaryRedColor, @@ -267,186 +333,15 @@ class _ActiveMedicationPageState extends State { ], ), ) -// Expanded( -// child: ListView( -// children: [ -// Container( -// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, -// hasShadow: true,), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Row( -// children: [ -// ClipRRect( -// borderRadius: BorderRadius.circular(1), -// child: Container( -// width: 59, -// height: 59, -// decoration: BoxDecoration( -// border: Border.all( -// color: AppColors.spacerLineColor,// Border color -// width: 1.0, ), -// borderRadius: BorderRadius.circular(30),// Border width -// ), -// child: -// Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) -// ), -// ), -// const SizedBox(width: 12), -// const Expanded( -// child: Text( -// "Diclofenac Diethylamine 1% Topical Gel", -// style: TextStyle( -// fontSize: 16, -// height: 1.2, -// fontWeight: FontWeight.w700, -// color: Colors.black87), -// ), -// ), -// ], -// ), -// const SizedBox(height: 12), -// Wrap( -// direction: Axis.horizontal, -// spacing: 4.h, -// runSpacing: 4.h, -// children: [ -// AppCustomChipWidget( -// labelText: "Route: Affected Area ", -// ), -// AppCustomChipWidget( -// labelText: "Route: Affected Area ", -// ), -// AppCustomChipWidget( -// labelText: "Daily Does 2", -// ), -// AppCustomChipWidget( -// labelText: "Route: Affected Area ", -// ), -// ], -// ), -// const SizedBox(height: 12), -// Row( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Icon(Icons.info_outline, color: Colors.grey,), -// const SizedBox(width: 8), -// Expanded( -// child: Text( -// "Remark: some remarks about the prescription will be here", -// style: TextStyle( -// fontSize: 10, -// color: AppColors.greyTextColor, -// fontWeight: FontWeight.w500, -// ), -// overflow: TextOverflow.visible, -// ), -// ) -// ], -// ), -// ], -// ).paddingAll(16), -// const Divider( -// indent: 0, -// endIndent: 0, -// thickness: 1, -// color: AppColors.greyColor, -// ), -// // Reminder Row -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// Row( -// children: [ -// Container( -// width: 40, -// height: 40, -// decoration: BoxDecoration( -// color: AppColors.greyColor, -// borderRadius: BorderRadius.circular(10),// Border width -// ), -// child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), -// const SizedBox(width: 8), -// Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// const Text( -// "Set Reminder", -// style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, -// color: AppColors.textColor), -// ), -// const Text( -// "Notify me before the consumption time", -// style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, -// color: AppColors.textColorLight), -// ), -// ], -// ), -// ], -// ), -// // Switch( -// // value: isActiveReminder, -// // onChanged: (_) {}, -// // activeColor: Colors.green, -// // ), -// ], -// ).paddingOnly(left:16, right: 16), -// const Divider( -// indent: 0, -// endIndent: 0, -// thickness: 1, -// color: AppColors.greyColor, -// ), -// -// // Buttons -// Row( -// children: [ -// Expanded( -// child: CustomButton( -// text: LocaleKeys.checkAvailability.tr(), -// fontSize: 14, -// onPressed: () async { -// }, -// backgroundColor: AppColors.secondaryLightRedColor, -// borderColor: AppColors.secondaryLightRedColor, -// textColor: AppColors.errorColor, -// ), -// ), -// const SizedBox(width: 12), -// Expanded( -// child: CustomButton( -// text: LocaleKeys.readInstructions.tr(), -// fontSize: 14, -// onPressed: () async { -// }, -// backgroundColor: AppColors.primaryRedColor, -// borderColor: AppColors.primaryRedColor, -// textColor: AppColors.whiteColor, -// ), -// ), -// ], -// ).paddingAll(16), -// ], -// ), -// ) -// // MedicationCard(), -// // SizedBox(height: 16), -// // MedicationCard(isActiveReminder: true), -// ], -// ), -// ), ] ), - ), + + ), ); } - Widget buildDayCard(String label, DateTime date) { + Widget buildDayCard(String label, DateTime date,) { bool isSelected = selectedDate.day == date.day && selectedDate.month == date.month && selectedDate.year == date.year; @@ -458,14 +353,14 @@ class _ActiveMedicationPageState extends State { }); }, child: Container( - width: 57, - height: 65, + width: 57.h, + height: 65.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: isSelected ? AppColors.secondaryLightRedBorderColor: AppColors.transparent, border: Border.all( color: isSelected ? AppColors.primaryRedBorderColor : AppColors.spacerLineColor, - width: 1.0, + width: 1.0.h, ), ), child: Padding( @@ -474,18 +369,18 @@ class _ActiveMedicationPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - date.day == currentDate.day ? "Today" : label, + date.day == currentDate.day ? "Today".needTranslation : label, style: TextStyle( color: isSelected ? AppColors.primaryRedBorderColor : AppColors.greyTextColor, - fontSize: 12, + fontSize: 12.fSize, fontWeight: FontWeight.w500, ), ), - const SizedBox(height: 5), + SizedBox(height: 5.h), Text( date.day.toString(), style: TextStyle( - fontSize: 16, + fontSize: 16.fSize, fontWeight: FontWeight.bold, color: isSelected ? AppColors.primaryRedBorderColor : AppColors.textColor, ), @@ -503,180 +398,167 @@ class _ActiveMedicationPageState extends State { if (day == 3 || day == 23) return "rd"; return "th"; } + + // Widget manageReminder(){ + // NavigationService navigationService = getIt(); + // return Container( + // width: 59, + // height: 59, + // decoration: BoxDecoration( + // border: Border.all( + // color: AppColors.spacerLineColor,// Border color + // width: 1.0, ), + // borderRadius: BorderRadius.circular(30),// Border width + // ), + // child: + // Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) + // ); + // } +} + +class ReminderTimerDialog extends StatefulWidget { + // final Function()? onSetReminderPress; + // final String message; + // + // const ReminderTimerDialog(this.onSetReminderPress, this.message, {super.key}); + const ReminderTimerDialog({super.key}); + + @override + State createState() => _ReminderTimerDialogState(); } -class MedicationCard extends StatelessWidget { - final bool isActiveReminder; - const MedicationCard({super.key, this.isActiveReminder = false}); +class _ReminderTimerDialogState extends State { + final List options = ["Morning", "Afternoon", "Evening", "Midnight"]; + final List selectedTimes = ["Morning"]; // Default selection - Color get primaryRed => const Color(0xFFE84B3A); @override Widget build(BuildContext context) { - return - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, - hasShadow: true,), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return // + Column( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(1), - child: Container( - width: 59, - height: 59, - decoration: BoxDecoration( - border: Border.all( - color: AppColors.spacerLineColor,// Border color - width: 1.0, ), - borderRadius: BorderRadius.circular(30),// Border width - ), - child: - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) - ), - ), - const SizedBox(width: 12), - const Expanded( - child: Text( - "Diclofenac Diethylamine 1% Topical Gel", + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, + hasShadow: true,), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + // Checkboxes list + children: options.map((time) => buildCircleCheckbox(time)).toList(), + ).paddingAll(16), + ), + SizedBox(height: 25.h), + // Buttons Row + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close, color: AppColors.errorColor), + label: Text( + LocaleKeys.cancel.tr(), style: TextStyle( - fontSize: 16, - height: 1.2, - fontWeight: FontWeight.w700, - color: Colors.black87), + color: AppColors.errorColor, + fontWeight: FontWeight.w500, + fontSize: 14.fSize + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.secondaryLightRedColor, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), ), - ], - ), - const SizedBox(height: 12), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "Route: Affected Area ", - ), - AppCustomChipWidget( - labelText: "Route: Affected Area ", - ), - AppCustomChipWidget( - labelText: "Daily Does 2", - ), - AppCustomChipWidget( - labelText: "Route: Affected Area ", - ), - ], - ), - const SizedBox(height: 12), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(Icons.info_outline, color: Colors.grey,), - const SizedBox(width: 8), - Expanded( - child: Text( - "Remark: some remarks about the prescription will be here", + ), + SizedBox(width: 12.h), + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.pop(context, selectedTimes); + }, + icon: const Icon(Icons.notifications_rounded), + label: Text( + LocaleKeys.setReminder.tr(), style: TextStyle( - fontSize: 10, - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w500, + fontSize: 14.fSize ), - overflow: TextOverflow.visible, ), - ) - ], - ), - ], - ).paddingAll(16), - const Divider( - indent: 0, - endIndent: 0, - thickness: 1, - color: AppColors.greyColor, - ), - // Reminder Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: AppColors.greyColor, - borderRadius: BorderRadius.circular(10),// Border width - ), - child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor)), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Set Reminder", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, - color: AppColors.textColor), - ), - const Text( - "Notify me before the consumption time", - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, - color: AppColors.textColorLight), - ), - ], + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.successColor, + foregroundColor: AppColors.whiteColor, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - ], - ), - Switch( - value: isActiveReminder, - onChanged: (_) {}, - activeColor: Colors.green, + ), ), - ], - ).paddingOnly(left:16, right: 16), - const Divider( - indent: 0, - endIndent: 0, - thickness: 1, - color: AppColors.greyColor, + ), + ], ), + SizedBox(height: 30.h), + ], + ); + } -// Buttons - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.checkAvailability.tr(), - fontSize: 14, - onPressed: () async { - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.errorColor, - ), - ), - const SizedBox(width: 12), - Expanded( - child: CustomButton( - text: LocaleKeys.readInstructions.tr(), - fontSize: 14, - onPressed: () async { - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - ), + Widget buildCircleCheckbox(String label) { + final bool isSelected = selectedTimes.contains(label); + return InkWell( + onTap: () { + setState(() { + if (isSelected) { + selectedTimes.remove(label); + } else { + selectedTimes.add(label); + } + }); + }, + borderRadius: BorderRadius.circular(25), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + // Custom circle checkbox + Container( + width: 15.h, + height: 15.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? AppColors.spacerLineColor: AppColors.spacerLineColor, + width: 1.h, ), - ], - ).paddingAll(16), - ], + color: isSelected ? AppColors.errorColor: AppColors.transparent, + ), + ), + SizedBox(width: 12.h), + // Label text + Text( + label, + style: TextStyle(fontSize: 16.fSize, color: Colors.black87), + ), + ], + ), ), ); } + + + void showCircleCheckboxDialog(BuildContext context) async { + final selected = await showDialog>( + context: context, + builder: (context) => const ReminderTimerDialog(), + ); + + if (selected != null && selected.isNotEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Reminders set for: ${selected.join(', ').needTranslation}')), + ); + } + } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 8d233b0..cf769f8 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -463,12 +463,6 @@ class _LandingPageState extends State { Row( children: [ "View all services".toText12(color: AppColors.primaryRedColor).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: ActiveMedicationPage(), - ), - ); }), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 1eff927..4dcf0c4 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; @@ -23,6 +24,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/active_medication/active_medication_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; @@ -56,6 +58,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; import '../prescriptions/prescription_detail_page.dart'; import 'widgets/medical_file_appointment_card.dart'; @@ -73,6 +76,7 @@ class _MedicalFilePageState extends State { late MedicalFileViewModel medicalFileViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late LabViewModel labViewModel; + late ActivePrescriptionsViewModel activePrescriptionsViewModel; int currentIndex = 0; @@ -98,6 +102,7 @@ class _MedicalFilePageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); medicalFileViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); + NavigationService navigationService = getIt.get(); return CollapsingListView( title: "Medical File".needTranslation, @@ -528,7 +533,13 @@ class _MedicalFilePageState extends State { Expanded( child: CustomButton( text: "All Medications".needTranslation, - onPressed: () {}, + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: ActiveMedicationPage(), + ), + ); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 7003a31..4d1b4be 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -14,6 +14,8 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/family_files/family_file_add_widget.dart'; +import '../widgets/medication_reminder/reminder_timer_dialog.dart'; + abstract class DialogService { Future showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed}); @@ -29,6 +31,8 @@ abstract class DialogService { Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}); Future showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}); + + Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); // TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here } @@ -133,6 +137,18 @@ class DialogServiceImp implements DialogService { ); } + @override + Future showReminderBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + showCommonBottomSheetWithoutHeight( + context, + title: label ?? "", + child: child ?? SizedBox(), + callBackFunc: () {}, + ); + } + @override Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { final context = navigationService.navigatorKey.currentContext; diff --git a/lib/widgets/medication_reminder/reminder_timer_dialog.dart b/lib/widgets/medication_reminder/reminder_timer_dialog.dart new file mode 100644 index 0000000..62dbd96 --- /dev/null +++ b/lib/widgets/medication_reminder/reminder_timer_dialog.dart @@ -0,0 +1,155 @@ +// import 'package:easy_localization/easy_localization.dart'; +// import 'package:flutter/material.dart'; +// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +// +// import '../../generated/locale_keys.g.dart'; +// import '../../theme/colors.dart'; +// +// class ReminderTimerDialog extends StatefulWidget { +// final Function()? onSetReminderPress; +// final String message; +// +// const ReminderTimerDialog(this.onSetReminderPress, this.message, {super.key}); +// +// +// @override +// State createState() => _ReminderTimerDialogState(); +// } +// +// class _ReminderTimerDialogState extends State { +// final List options = ["Morning", "Afternoon", "Evening", "Midnight"]; +// final List selectedTimes = ["Morning"]; // Default selection +// +// +// @override +// Widget build(BuildContext context) { +// return // +// Column( +// children: [ +// Container( +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, +// hasShadow: true,), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// // Checkboxes list +// children: options.map((time) => buildCircleCheckbox(time)).toList(), +// ).paddingAll(16), +// ), +// const SizedBox(height: 25), +// // Buttons Row +// Row( +// children: [ +// Expanded( +// child: ElevatedButton.icon( +// onPressed: () => Navigator.pop(context), +// icon: const Icon(Icons.close, color: AppColors.errorColor), +// label: Text( +// LocaleKeys.cancel.tr(), +// style: TextStyle( +// color: AppColors.errorColor, +// fontWeight: FontWeight.w500, +// fontSize: 14 +// ), +// ), +// style: ElevatedButton.styleFrom( +// backgroundColor: AppColors.secondaryLightRedColor, +// elevation: 0, +// padding: const EdgeInsets.symmetric(vertical: 14), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(12), +// ), +// ), +// ), +// ), +// const SizedBox(width: 12), +// Expanded( +// child: ElevatedButton.icon( +// onPressed: () { +// Navigator.pop(context, selectedTimes); +// }, +// icon: const Icon(Icons.notifications_rounded), +// label: Text( +// LocaleKeys.setReminder.tr(), +// style: TextStyle( +// fontWeight: FontWeight.w500, +// fontSize: 14 +// ), +// ), +// style: ElevatedButton.styleFrom( +// backgroundColor: AppColors.successColor, +// foregroundColor: AppColors.whiteColor, +// elevation: 0, +// padding: const EdgeInsets.symmetric(vertical: 14), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(12), +// ), +// ), +// ), +// ), +// ], +// ), +// const SizedBox(height: 30), +// ], +// ); +// } +// +// Widget buildCircleCheckbox(String label) { +// final bool isSelected = selectedTimes.contains(label); +// return InkWell( +// onTap: () { +// setState(() { +// if (isSelected) { +// selectedTimes.remove(label); +// } else { +// selectedTimes.add(label); +// } +// }); +// }, +// borderRadius: BorderRadius.circular(25), +// child: Padding( +// padding: const EdgeInsets.symmetric(vertical: 8.0), +// child: Row( +// children: [ +// // Custom circle checkbox +// Container( +// width: 15, +// height: 15, +// decoration: BoxDecoration( +// shape: BoxShape.circle, +// border: Border.all( +// color: isSelected ? AppColors.spacerLineColor: AppColors.spacerLineColor, +// width: 1, +// ), +// color: isSelected ? AppColors.errorColor: AppColors.transparent, +// ), +// ), +// const SizedBox(width: 12), +// // Label text +// Text( +// label, +// style: const TextStyle(fontSize: 16, color: Colors.black87), +// ), +// ], +// ), +// ), +// ); +// } +// +// +// void showCircleCheckboxDialog(BuildContext context) async { +// final selected = await showDialog>( +// context: context, +// builder: (context) => const ReminderTimerDialog(), +// ); +// +// if (selected != null && selected.isNotEmpty) { +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Reminders set for: ${selected.join(', ')}')), +// ); +// } +// } +// } +// +// +// From eae16eec44f5a702b59801f4c36209279f780f0b Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Mon, 17 Nov 2025 09:38:20 +0300 Subject: [PATCH 03/46] active medication --- lib/core/api_consts.dart | 3 +- lib/core/dependencies.dart | 12 + lib/core/utils/calendar_utils.dart | 2 +- lib/core/utils/utils.dart | 2 +- .../active_prescriptions_repo.dart | 58 +- .../active_prescriptions_view_model.dart | 168 ++- .../active_prescriptions_response_model.dart | 94 +- lib/main.dart | 4 + .../active_medication_page.dart | 1129 ++++++++++------- .../call_ambulance/tracking_screen.dart | 14 +- .../widgets/nearestERItem.dart | 2 +- lib/presentation/home/landing_page.dart | 11 +- lib/theme/colors.dart | 2 + 13 files changed, 951 insertions(+), 550 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index ed2bba1..6dab797 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -729,7 +729,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -848,6 +848,7 @@ class ApiConsts { static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; + static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a82a9ad..67464b3 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -3,6 +3,8 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; @@ -45,6 +47,8 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../features/active_prescriptions/active_prescriptions_repo.dart'; + GetIt getIt = GetIt.instance; class AppDependencies { @@ -103,6 +107,7 @@ class AppDependencies { getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -202,6 +207,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => ActivePrescriptionsViewModel( + errorHandlerService: getIt(), + activePrescriptionsRepo: getIt() + ), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( // () => BookAppointmentsViewModel( diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 2068db9..8c0db18 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -266,7 +266,7 @@ setCalender(BuildContext context, eventId: eventId + (i.toString() + j.toString()), location: '', //event id with varitions ); - + print("Creating event #$j for day $i → $actualDate"); actualDate = DateTime(actualDate.year, actualDate.month, actualDate.day, 8, 0); } actualDate = Jiffy.parseFromDateTime(actualDate).add(days: 1).dateTime; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index a88d9c2..1fc3806 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -670,7 +670,7 @@ class Utils { } /// Widget to build an SVG from network - static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover, ImageErrorWidgetBuilder? errorBuilder}) { + static Widget buildImgWithNetwork({required String url, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover, ImageErrorWidgetBuilder? errorBuilder}) { return Image.network( url, width: width, diff --git a/lib/features/active_prescriptions/active_prescriptions_repo.dart b/lib/features/active_prescriptions/active_prescriptions_repo.dart index 847d6e8..437f364 100644 --- a/lib/features/active_prescriptions/active_prescriptions_repo.dart +++ b/lib/features/active_prescriptions/active_prescriptions_repo.dart @@ -2,7 +2,6 @@ import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart'; - import '../../core/api/api_client.dart'; import '../../core/api_consts.dart'; import '../../core/common_models/generic_api_model.dart'; @@ -11,7 +10,7 @@ import '../../services/logger_service.dart'; abstract class ActivePrescriptionsRepo { - Future>> getActivePrescriptionsDetails(); + Future>>> getActivePrescriptionsDetails(); } @@ -23,10 +22,10 @@ class ActivePrescriptionsRepoImp implements ActivePrescriptionsRepo { @override - Future>> getActivePrescriptionsDetails() async + Future>>> getActivePrescriptionsDetails() async { try { - GenericApiModel? apiResponse; + GenericApiModel>? apiResponse; Failure? failure; await apiClient.post( ApiConsts.getActivePrescriptionsDetails, @@ -36,18 +35,20 @@ class ActivePrescriptionsRepoImp implements ActivePrescriptionsRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - // final list = response['GetActivePrescriptionReportByPatientIDList']; - - // final prescriptionLists = list.map((item) => ActivePrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); + var list = response['List_ActiveGetPrescriptionReportByPatientID']; + var res = list + .map( + (item) => ActivePrescriptionsResponseModel.fromJson(item)) + .toList(); - apiResponse = GenericApiModel( + apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, - data: response, + // data: response, + data: res ); - return ['List_ActiveGetPrescriptionReportByPatientID']; - //apiResponse; + return apiResponse; } catch (e) { failure = DataParsingFailure(e.toString()); } @@ -61,39 +62,4 @@ class ActivePrescriptionsRepoImp implements ActivePrescriptionsRepo { } } - - - - // - // Future> getActiveMedications() { - // try { - // GenericApiModel? apiResponse; - // Failure? failure; - // return apiClient.post( - // ApiConsts.getActivePrescriptionsDetails, - // body: patientDeviceDataRequest, - // onFailure: (error, statusCode, {messageStatus, failureType}) { - // failure = failureType; - // }, - // onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - // try { - // apiResponse = GenericApiModel( - // messageStatus: messageStatus, - // statusCode: statusCode, - // errorMessage: errorMessage, - // data: response, - // ); - // } catch (e) { - // failure = DataParsingFailure(e.toString()); - // } - // }, - // ).then((_) { - // if (failure != null) return Left(failure!); - // if (apiResponse == null) return Left(ServerFailure("Unknown error")); - // return Right(apiResponse!); - // }); - // } catch (e) { - // return Future.value(Left(UnknownFailure(e.toString()))); - // } - // } } \ No newline at end of file diff --git a/lib/features/active_prescriptions/active_prescriptions_view_model.dart b/lib/features/active_prescriptions/active_prescriptions_view_model.dart index d995f96..03f84ea 100644 --- a/lib/features/active_prescriptions/active_prescriptions_view_model.dart +++ b/lib/features/active_prescriptions/active_prescriptions_view_model.dart @@ -4,13 +4,17 @@ import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_ import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; -class ActivePrescriptionsViewModel extends ChangeNotifier { +class ActivePrescriptionsViewModel extends ChangeNotifier { bool isActivePrescriptionsDetailsLoading = false; - late ActivePrescriptionsRepo activePrescriptionsRepo; + late ActivePrescriptionsRepo activePrescriptionsRepo; late ErrorHandlerService errorHandlerService; - // Prescription Orders Lists + ActivePrescriptionsViewModel({ + required this.activePrescriptionsRepo, + required this.errorHandlerService, + }); + List activePrescriptionsDetailsList = []; initActivePrescriptionsViewModel() { @@ -20,38 +24,172 @@ class ActivePrescriptionsViewModel extends ChangeNotifier { setPrescriptionsDetailsLoading() { isActivePrescriptionsDetailsLoading = true; - // activePrescriptionsDetailsList.clear(); notifyListeners(); } - Future getActiveMedications( {Function(dynamic)? onSuccess, Function(String)? onError}) - async { + // Get medications list + Future getActiveMedications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { final result = await activePrescriptionsRepo.getActivePrescriptionsDetails(); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { - if (apiResponse.messageStatus == 2) { - // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); - } else if (apiResponse.messageStatus == 1) { - activePrescriptionsDetailsList = apiResponse.data!; - isActivePrescriptionsDetailsLoading = false; + if (apiResponse.messageStatus == 1) { + activePrescriptionsDetailsList = apiResponse.data ?? []; notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); - print(activePrescriptionsDetailsList.length); - } + if (onSuccess != null) onSuccess(apiResponse.data); } }, ); } + DateTime parseDate(String? date) { + if (date == null) return DateTime.now(); + final regex = RegExp(r"\/Date\((\d+)([+-]\d+)?\)\/"); + final match = regex.firstMatch(date); + if (match != null) { + final millis = int.parse(match.group(1)!); + return DateTime.fromMillisecondsSinceEpoch(millis); + } + return DateTime.tryParse(date) ?? DateTime.now(); + } + // Extract numeric value ( "3 / week" → 3) + int extractNumberFromFrequency(String? frequency) { + if (frequency == null) return 1; + final m = RegExp(r'(\d+)').firstMatch(frequency); + if (m != null) return int.tryParse(m.group(1)!) ?? 1; + return 1; + } + // Generate medication days based on frequency text + List generateMedicationDays(ActivePrescriptionsResponseModel med) { + final start = parseDate(med.startDate); + final duration = med.days ?? 0; + final frequency = (med.frequency ?? "").toLowerCase().trim(); + + List result = []; + if (duration <= 0) return result; + + // Every N hours ( "Every Six Hours", "Every 8 hours") + if (frequency.contains("hour")) { + final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); + int intervalHours = 0; + + if (match != null) { + intervalHours = int.tryParse(match.group(1)!) ?? 0; + } else { + // handle text numbers like "Every six hours" + final textNum = { + "one": 1, + "two": 2, + "three": 3, + "four": 4, + "five": 5, + "six": 6, + "seven": 7, + "eight": 8, + "nine": 9, + "ten": 10, + "twelve": 12, + }; + for (var key in textNum.keys) { + if (frequency.contains(key)) { + intervalHours = textNum[key]!; + break; + } + } + } + if (intervalHours > 0) { + for (int day = 0; day < duration; day++) { + final dayStart = start.add(Duration(days: day)); + for (int hour = 0; hour < 24; hour += intervalHours) { + result.add(DateTime(dayStart.year, dayStart.month, dayStart.day, hour)); + } + } + return result; + } + } + + // Daily (every day) + if (frequency.contains("day") && + !frequency.contains("every other") && + !frequency.contains("every ")) { + for (int i = 0; i < duration; i++) { + result.add(start.add(Duration(days: i))); + } + } + + // Every other day + else if (frequency.contains("every other day")) { + for (int i = 0; i < duration; i += 2) { + result.add(start.add(Duration(days: i))); + } + } + + // Every N days → e.g. "Every 3 days", "Every 5 days" + else if (frequency.contains("every") && frequency.contains("day")) { + final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); + final interval = match != null ? int.tryParse(match.group(1)!) ?? 1 : 1; + for (int i = 0; i < duration; i += interval) { + result.add(start.add(Duration(days: i))); + } + } + + // Once or twice a week + else if (frequency.contains("once a week")) { + for (int i = 0; i < duration; i += 7) { + result.add(start.add(Duration(days: i))); + } + } else if (frequency.contains("twice a week")) { + for (int i = 0; i < duration; i += 3) { + result.add(start.add(Duration(days: i))); + } + } + + // Numeric frequency like "3 / week", "2 / week" + else if (frequency.contains("week")) { + int timesPerWeek = extractNumberFromFrequency(frequency); + double interval = 7 / timesPerWeek; + double dayPointer = 0; + + for (int i = 0; i < duration; i++) { + if (i >= dayPointer.floor()) { + result.add(start.add(Duration(days: i))); + dayPointer += interval; + } + } + } + + else { + result.add(start); + } + final unique = {}; + for (final d in result) { + unique["${d.year}-${d.month}-${d.day}"] = d; + } + return unique.values.toList()..sort((a, b) => a.compareTo(b)); + } + bool sameYMD(DateTime a, DateTime b) => + a.year == b.year && a.month == b.month && a.day == b.day; + // Filter medications for selected day + List getMedsForSelectedDay(DateTime selectedDate) { + final target = DateTime(selectedDate.year, selectedDate.month, selectedDate.day); + return activePrescriptionsDetailsList.where((med) { + final days = generateMedicationDays(med); + return days.any((d) => sameYMD(d, target)); + }).toList(); + } } + + + diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart index 878e191..eb216a6 100644 --- a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -7,7 +7,7 @@ class ActivePrescriptionsResponseModel { dynamic companyName; int? days; dynamic doctorName; - int? doseDailyQuantity; + int? doseDailyQuantity; // doses per day String? frequency; int? frequencyNumber; dynamic image; @@ -23,7 +23,7 @@ class ActivePrescriptionsResponseModel { dynamic patientName; dynamic phoneOffice1; dynamic prescriptionQr; - int? prescriptionTimes; + dynamic prescriptionTimes; dynamic productImage; String? productImageBase64; String? productImageString; @@ -35,6 +35,10 @@ class ActivePrescriptionsResponseModel { int? scaleOffset; String? startDate; + // ✅ Added for reminder feature + List selectedDoseTimes = []; + bool isReminderOn = false; // toggle status + ActivePrescriptionsResponseModel({ this.address, this.appointmentNo, @@ -69,47 +73,57 @@ class ActivePrescriptionsResponseModel { this.sku, this.scaleOffset, this.startDate, - }); - factory ActivePrescriptionsResponseModel.fromRawJson(String str) => ActivePrescriptionsResponseModel.fromJson(json.decode(str)); + // ✅ Default values for new fields (won’t break API) + List? selectedDoseTimes, + this.isReminderOn = false, + }) : selectedDoseTimes = selectedDoseTimes ?? []; + + factory ActivePrescriptionsResponseModel.fromRawJson(String str) => + ActivePrescriptionsResponseModel.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); - factory ActivePrescriptionsResponseModel.fromJson(Map json) => ActivePrescriptionsResponseModel( - address: json["Address"], - appointmentNo: json["AppointmentNo"], - clinic: json["Clinic"], - companyName: json["CompanyName"], - days: json["Days"], - doctorName: json["DoctorName"], - doseDailyQuantity: json["DoseDailyQuantity"], - frequency: json["Frequency"], - frequencyNumber: json["FrequencyNumber"], - image: json["Image"], - imageExtension: json["ImageExtension"], - imageSrcUrl: json["ImageSRCUrl"], - imageString: json["ImageString"], - imageThumbUrl: json["ImageThumbUrl"], - isCovered: json["IsCovered"], - itemDescription: json["ItemDescription"], - itemId: json["ItemID"], - orderDate: json["OrderDate"], - patientId: json["PatientID"], - patientName: json["PatientName"], - phoneOffice1: json["PhoneOffice1"], - prescriptionQr: json["PrescriptionQR"], - prescriptionTimes: json["PrescriptionTimes"], - productImage: json["ProductImage"], - productImageBase64: json["ProductImageBase64"], - productImageString: json["ProductImageString"], - projectId: json["ProjectID"], - projectName: json["ProjectName"], - remarks: json["Remarks"], - route: json["Route"], - sku: json["SKU"], - scaleOffset: json["ScaleOffset"], - startDate: json["StartDate"], - ); + factory ActivePrescriptionsResponseModel.fromJson(Map json) => + ActivePrescriptionsResponseModel( + address: json["Address"], + appointmentNo: json["AppointmentNo"], + clinic: json["Clinic"], + companyName: json["CompanyName"], + days: json["Days"], + doctorName: json["DoctorName"], + doseDailyQuantity: json["DoseDailyQuantity"], + frequency: json["Frequency"], + frequencyNumber: json["FrequencyNumber"], + image: json["Image"], + imageExtension: json["ImageExtension"], + imageSrcUrl: json["ImageSRCUrl"], + imageString: json["ImageString"], + imageThumbUrl: json["ImageThumbUrl"], + isCovered: json["IsCovered"], + itemDescription: json["ItemDescription"], + itemId: json["ItemID"], + orderDate: json["OrderDate"], + patientId: json["PatientID"], + patientName: json["PatientName"], + phoneOffice1: json["PhoneOffice1"], + prescriptionQr: json["PrescriptionQR"], + prescriptionTimes: json["PrescriptionTimes"], + productImage: json["ProductImage"], + productImageBase64: json["ProductImageBase64"], + productImageString: json["ProductImageString"], + projectId: json["ProjectID"], + projectName: json["ProjectName"], + remarks: json["Remarks"], + route: json["Route"], + sku: json["SKU"], + scaleOffset: json["ScaleOffset"], + startDate: json["StartDate"], + + // ✅ Ensure local reminder values are not overwritten by API + selectedDoseTimes: [], + isReminderOn: false, + ); Map toJson() => { "Address": address, @@ -145,5 +159,7 @@ class ActivePrescriptionsResponseModel { "SKU": sku, "ScaleOffset": scaleOffset, "StartDate": startDate, + + }; } diff --git a/lib/main.dart b/lib/main.dart index 259ce3b..5a33d02 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; @@ -129,6 +130,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index 6394002..aa2abe5 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -1,355 +1,538 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -// import 'package:sizer/sizer.dart'; -import '../../core/dependencies.dart'; +import 'package:flutter/cupertino.dart'; +import '../../core/app_assets.dart'; +import '../../core/utils/calendar_utils.dart'; import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; import '../../features/active_prescriptions/models/active_prescriptions_response_model.dart'; import '../../generated/locale_keys.g.dart'; -import '../../services/dialog_service.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import 'package:intl/intl.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; -// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; - import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; // for date formatting import 'package:provider/provider.dart'; - +import '../../widgets/loader/bottomsheet_loader.dart'; class ActiveMedicationPage extends StatefulWidget { - //inal List activePrescriptionsResponseModel; - - ActiveMedicationPage({super.key, }); - - - + const ActiveMedicationPage({super.key}); @override State createState() => _ActiveMedicationPageState(); } class _ActiveMedicationPageState extends State { - late DateTime currentDate; - late DateTime selectedDate; - - + late DateTime currentDate; + late DateTime selectedDate; + List selectedDayMeds = []; ActivePrescriptionsViewModel? activePreVM; + Map medReminderStatus = {}; @override - void initState() { - activePreVM = Provider.of(context, listen: false); - activePreVM?.getActiveMedications(); - print(activePreVM?.activePrescriptionsDetailsList); + void initState() { super.initState(); currentDate = DateTime.now(); selectedDate = currentDate; + WidgetsBinding.instance.addPostFrameCallback((_) async { + activePreVM = Provider.of(context, listen: false); + LoaderBottomSheet.showLoader(); + await activePreVM!.getActiveMedications( + onSuccess: (_) { + LoaderBottomSheet.hideLoader(); + final todayMeds = activePreVM!.getMedsForSelectedDay(selectedDate); + setState(() { + selectedDayMeds = todayMeds; + }); + }, + onError: (_) { + LoaderBottomSheet.hideLoader(); + }, + ); + activePreVM!.addListener(() { + if (!mounted) return; + final medsForDay = activePreVM!.getMedsForSelectedDay(selectedDate); + setState(() => selectedDayMeds = medsForDay); + }); + }); } -// Generate today + next 6 days - List getUpcomingDays() { - return List.generate(7, (index) => currentDate.add(Duration(days: index))); - } - // on/off toggle - bool isOn = true; - - get index => null; + List getUpcomingDays() => List.generate(7, (index) => currentDate.add(Duration(days: index))); @override Widget build(BuildContext context) { - // activePreVM = Provider.of(context, listen: false); - List days = getUpcomingDays(); - int dayIndex = selectedDate.difference(currentDate).inDays; - String dateText = "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)} "; - return Scaffold( + final days = getUpcomingDays(); + final dateText = "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)}"; + return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( - onBackPressed: () { - Navigator.of(context).pop(); - }, - onLanguageChanged: (lang) {}, - hideLogoAndLang: true, + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 65.h, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: days.length, - // itemCount: widget.details.length, - itemBuilder: (context, index) { - DateTime day = days[index]; - String label = DateFormat('E').format(day); // Mon, Tue - return Padding( - padding: const EdgeInsets.only(right: 12), - child: buildDayCard(label, day), - ); - }, - ), - ), - SizedBox(height: 20.h), - -// Show full date text - Text( - dateText, + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Active Medications".needTranslation, style: TextStyle( color: AppColors.textColor, - fontSize: 16.fSize, - fontWeight: FontWeight.w500), + fontSize: 27.f, + fontWeight: FontWeight.w600)), + SizedBox(height: 16.h), + SizedBox( + height: 65.h, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: days.length, + itemBuilder: (context, index) { + final day = days[index]; + final label = DateFormat('E').format(day); + return Padding( + padding: const EdgeInsets.only(right: 12), + child: buildDayCard(label, day), + ); + }, ), - Text( - "Medications".needTranslation, - style: TextStyle( - color: AppColors.primaryRedBorderColor,fontSize: 12.fSize, fontWeight: FontWeight.w500), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, - hasShadow: true,), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + SizedBox(height: 20.h), + RichText( + text: TextSpan( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(1), - child: Container( - width: 59.h, - height: 59.h, - decoration: BoxDecoration( - border: Border.all( - color: AppColors.spacerLineColor,// Border color - width: 1.0.h, ), - borderRadius: BorderRadius.circular(30),// Border width - ), - child: - Utils.buildImgWithNetwork(url: activePreVM!.activePrescriptionsDetailsList[index].productImageString.toString(),width: 26.h,) - ), - ), - SizedBox(width: 12.h), - Text( - activePreVM!.activePrescriptionsDetailsList[index].itemDescription.toString(), - style: TextStyle( - fontSize: 16.fSize, - height: 1.2.h, - fontWeight: FontWeight.w700, - color: Colors.black87), - ), - ], - ), - SizedBox(height: 12.h), - activePreVM!.activePrescriptionsDetailsList.length > 0 ? - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "Route: ${activePreVM?.activePrescriptionsDetailsList[index].route}", - ), - AppCustomChipWidget( - labelText: "Frequency: ${activePreVM?.activePrescriptionsDetailsList[index].frequency}".needTranslation, - ), - AppCustomChipWidget( - labelText: "Daily Does ${activePreVM?.activePrescriptionsDetailsList[index].doseDailyQuantity}".needTranslation, - ), - AppCustomChipWidget( - labelText: "Duration: ${activePreVM?.activePrescriptionsDetailsList[index].days} ".needTranslation, - ), - ], - ): - Container( - child: Text("no data"), - ), - SizedBox(height: 12.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(Icons.info_outline, color: Colors.grey,), - SizedBox(width: 8.h), - Expanded( - child: Text( - "Remark: some remarks about the prescription will be here".needTranslation, - style: TextStyle( - fontSize: 10.fSize, - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, - ), - overflow: TextOverflow.visible, - ), - ) - ], - ), - ], - ).paddingAll(16), - const Divider( - indent: 0, - endIndent: 0, - thickness: 1, - color: AppColors.greyColor, + TextSpan( + text: "${selectedDate.day}", + style: TextStyle( + color: AppColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), - // Reminder Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 40.h, - height: 40.h, - decoration: BoxDecoration( - color: AppColors.greyColor, - borderRadius: BorderRadius.circular(10),// Border width - ), - child: Icon(Icons.notifications_sharp, color: AppColors.greyTextColor) - // MedicalFileCard( - // label: "Vaccine Info".needTranslation, - // textColor: AppColors.blackColor, - // backgroundColor: AppColors.whiteColor, - // svgIcon: AppAssets..bell, - // isLargeText: true, - // iconSize: 36.h, - // ) + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -4), + child: Text( + getSuffix(selectedDate.day), + style: const TextStyle( + fontSize: 12, + color: AppColors.textColor, + fontWeight: FontWeight.w500, + ), ), - SizedBox(width: 8.h), - Expanded( + ), + ), + TextSpan( + text: " ${DateFormat.MMMM().format(selectedDate)}", + style: const TextStyle( + color: AppColors.textColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Text("Medications".needTranslation, + style: TextStyle( + color: AppColors.primaryRedBorderColor, + fontSize: 12.f, + fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + Expanded( + child: SingleChildScrollView( + child: selectedDayMeds.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: selectedDayMeds.length, + itemBuilder: (context, index) { + final med = selectedDayMeds[index]; + final doses = med.doseDailyQuantity ?? 1; + med.selectedDoseTimes ??= List.filled(doses, null); + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + margin: EdgeInsets.all(10), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Set Reminder", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14.fSize, - color: AppColors.textColor), - ), - Text( - "Notify me before the consumption time", - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12.fSize, - color: AppColors.textColorLight), + _buildMedHeader(med), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.info_outline, + color: AppColors.lightGreyTextColor, + size: 20, + ), + SizedBox(width: 6.h), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Remarks: ".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + TextSpan( + text: "some remarks about the prescription will be here".needTranslation, + style: TextStyle( + color: AppColors.lightGreyTextColor, + fontWeight: FontWeight.normal, + fontSize: 10, + ), + ), + ], + ), + ), + ), + ], + ).paddingOnly(left: 16, right: 16), + const Divider(color: AppColors.greyColor), + // Reminder Section + GestureDetector( + onTap: () => showDoseDialog(med, index), + child: Row( + children: [ + Container( + width: 40.h, + height: 40.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(10), + ), + child: Utils.buildSvgWithAssets( + icon: AppAssets.bell, + height: 24.h, + width: 24.h, + iconColor: AppColors.greyTextColor, + ), + ), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text("Set Reminder".needTranslation, + style: TextStyle( + fontSize: 14.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor)), + Text("Notify me before the consumption time".needTranslation, + style: TextStyle( + fontSize: 12.f, + color: AppColors.textColorLight, + )), + ], + ), + ), + _buildToggle(index) + ], + ).paddingAll(16), ), + const Divider(color: AppColors.greyColor), + _buildButtons(), ], - ).onPress(() { - DialogService dialogService = getIt.get(); - dialogService.showReminderBottomSheetWithoutHWithChild( - label: "Set the timer for reminder".needTranslation, - message: "", - child: ReminderTimerDialog(), - onOkPressed: () {}, - ); - }), + ), + ); + }, + ) + : Utils.getNoDataWidget(context, + noDataText: "No medications today".needTranslation), + ), + ), + ], + ).paddingAll(16), + ); + } + + //medicine card + Widget _buildMedHeader(ActivePrescriptionsResponseModel med) => Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Container( + width: 59.h, + height: 59.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.spacerLineColor, + borderRadius: 30.r, + hasShadow: false, + ), + child: Utils.buildImgWithNetwork( + url: med.productImageString ?? "" ).circle(52.h) + ), + ), + SizedBox(width: 12.h), + Expanded( + child: Text( + med.itemDescription ?? "", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor), + ), + ), + ]), + SizedBox(height: 12.h), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + AppCustomChipWidget(labelText: "Route: ${med.route}".needTranslation), + AppCustomChipWidget(labelText: "Frequency: ${med.frequency}".needTranslation), + AppCustomChipWidget(labelText: "Daily Dose: ${med.doseDailyQuantity}".needTranslation), + AppCustomChipWidget(labelText: "Duration: ${med.days}".needTranslation), + ], + ), + ], + ), + ); + + Widget _buildButtons() => Padding( + padding: EdgeInsets.all(16), + child: Row(children: [ + Expanded( + child: CustomButton( + text: "Check Availability".needTranslation, + fontSize: 13.f, + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + ), + SizedBox(width: 12.h), + Expanded( + child: CustomButton( + text: "Read Instructions".needTranslation, fontSize: 13.f, onPressed: () {})), + ]), + ); + + Widget _buildToggle(int index) { + final value = medReminderStatus[index] ?? false; + + return GestureDetector( + onTap: () async { + await showDoseDialog(selectedDayMeds[index], index); + setState(() { + if ((selectedDayMeds[index].selectedDoseTimes ?? []).any((t) => t != null)) { + medReminderStatus[index] = true; + } + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 50.h, + height: 28.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: value ? AppColors.lightGreenColor : AppColors.greyColor.withOpacity(0.3), + ), + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + alignment: value ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(3), + child: Container( + width: 22.h, + height: 22.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: value ? AppColors.textGreenColor : AppColors.greyTextColor, + ), + ), + ), + ), + ), + ); + } + + Future showDoseDialog(ActivePrescriptionsResponseModel med, int medIndex) { + final doses = med.frequencyNumber ?? 1; + if (med.selectedDoseTimes.length != doses) { + med.selectedDoseTimes = List.generate(doses, (_) => null); + } + + return showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => Container( + width: double.infinity, + height: 520.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bottomSheetBgColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + hasShadow: true, + ), + + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Reminders".needTranslation, + style: TextStyle( + fontSize: 20.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor, ), - GestureDetector( - onTap: () { - setState(() { - isOn = !isOn; - }); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 50.h, - height: 28.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: isOn ? AppColors.lightGreenColor: AppColors.greyColor, ), - child: AnimatedAlign( - duration: const Duration(milliseconds: 200), - alignment: isOn ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(3), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color:AppColors.blackBgColor), + ), + ], + ), + SizedBox(height: 20.h), + Expanded( + child: ListView.builder( + itemCount: doses, + itemBuilder: (context, doseIndex) { + final badgeColor = [ + AppColors.textGreenColor, + AppColors.infoColor, + AppColors.labelColorYellow, + AppColors.purpleBg + ][doseIndex % 4]; + + final doseLabel = "${doseIndex + 1}${getSuffix(doseIndex + 1)}"; + final time = med.selectedDoseTimes[doseIndex] ?? "Not set yet"; + + return GestureDetector( + onTap: () { + Navigator.pop(context); + showTimePickerSheet(med, medIndex, doseIndex); + }, child: Container( - width: 22.h, - height: 22.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isOn ? AppColors.textGreenColor : AppColors.greyTextColor, + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + vertical: 6, horizontal: 14), + decoration: BoxDecoration( + color: badgeColor, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + doseLabel, + style: TextStyle( + color: AppColors.whiteColor, + fontWeight: FontWeight.bold, + fontSize: 16.f, + ), + ), + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "Set reminder for $doseLabel dose", + style: TextStyle( + color: AppColors.textColor, + fontWeight: FontWeight.bold, + fontSize: 16.f, + ), + ), + ), + Icon(Icons.arrow_forward_outlined, + size: 24.w, color: AppColors.textColor), + ], + ), + SizedBox(height: 4.h), + Text( + time, + style: TextStyle( + fontSize: 12.f, + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + ), + ], ), ), - ), - ), + ); + }, ), ), - SizedBox(width: 2.h), - // Switch( - // value: isOn, - // onChanged: (value){ - // setState(() { - // isOn = value; - // }); - // }, - // activeColor: AppColors.lightGreenColor, - // activeTrackColor: AppColors.lightGreenColor, - // activeThumbColor: AppColors.textGreenColor, - // inactiveThumbColor: AppColors.greyTextColor, - // inactiveTrackColor: AppColors.greyColor, - // ), - ], - ).paddingAll(16), - const Divider( - indent: 0, - endIndent: 0, - thickness: 1, - color: AppColors.greyColor, - ), - -// Buttons - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.checkAvailability.tr(), - fontSize: 14.fSize, - onPressed: () async { - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.errorColor, - ), - ), - SizedBox(width: 12.h), - Expanded( - child: CustomButton( - text: LocaleKeys.readInstructions.tr(), - fontSize: 14.fSize, - onPressed: () async { - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - ), - ), - ], - ).paddingAll(16), - ], - ), - ) - ] - ), + ], + ), + ), + ), + ); + } + void showTimePickerSheet( + ActivePrescriptionsResponseModel med, int medIndex, int doseIndex) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => Container( + width: double.infinity, + height: 460.h, + decoration: BoxDecoration( + color: AppColors.bottomSheetBgColor, + borderRadius: + BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + ), + child: ReminderTimerDialog( + med: med, + frequencyNumber: med.doseDailyQuantity ?? 1, + doseIndex: doseIndex, + onTimeSelected: (String time) { + setState(() { + med.selectedDoseTimes[doseIndex] = time; + medReminderStatus[medIndex] = true; + }); + }, + ), ), ); } - Widget buildDayCard(String label, DateTime date,) { - bool isSelected = selectedDate.day == date.day && + Widget buildDayCard(String label, DateTime date) { + final isSelected = selectedDate.day == date.day && selectedDate.month == date.month && selectedDate.year == date.year; - return GestureDetector( onTap: () { + final vm = + Provider.of(context, listen: false); setState(() { selectedDate = date; + selectedDayMeds = vm.getMedsForSelectedDay(date); }); }, child: Container( @@ -357,36 +540,38 @@ class _ActiveMedicationPageState extends State { height: 65.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: isSelected ? AppColors.secondaryLightRedBorderColor: AppColors.transparent, + color: isSelected + ? AppColors.secondaryLightRedBorderColor + : Colors.transparent, border: Border.all( - color: isSelected ? AppColors.primaryRedBorderColor : AppColors.spacerLineColor, - width: 1.0.h, - ), + color: isSelected + ? AppColors.primaryRedBorderColor + : AppColors.spacerLineColor, + width: 1), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - date.day == currentDate.day ? "Today".needTranslation : label, - style: TextStyle( - color: isSelected ? AppColors.primaryRedBorderColor : AppColors.greyTextColor, - fontSize: 12.fSize, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 5.h), - Text( - date.day.toString(), - style: TextStyle( - fontSize: 16.fSize, - fontWeight: FontWeight.bold, - color: isSelected ? AppColors.primaryRedBorderColor : AppColors.textColor, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + date.day == currentDate.day ? "Today" : label, + style: TextStyle( + color: isSelected + ? AppColors.primaryRedBorderColor + : AppColors.greyTextColor, + fontSize: 11.f, + fontWeight: FontWeight.w500), ), - ), - ], - ), + SizedBox(height: 5.h), + Text("${date.day}", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.bold, + color: isSelected + ? AppColors.primaryRedBorderColor + : AppColors.textColor)) + ]), ), ), ); @@ -398,167 +583,235 @@ class _ActiveMedicationPageState extends State { if (day == 3 || day == 23) return "rd"; return "th"; } - - // Widget manageReminder(){ - // NavigationService navigationService = getIt(); - // return Container( - // width: 59, - // height: 59, - // decoration: BoxDecoration( - // border: Border.all( - // color: AppColors.spacerLineColor,// Border color - // width: 1.0, ), - // borderRadius: BorderRadius.circular(30),// Border width - // ), - // child: - // Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon,width: 30.h, height: 30.h) - // ); - // } } + + class ReminderTimerDialog extends StatefulWidget { - // final Function()? onSetReminderPress; - // final String message; - // - // const ReminderTimerDialog(this.onSetReminderPress, this.message, {super.key}); - const ReminderTimerDialog({super.key}); + final int frequencyNumber; + final int doseIndex; + final Function(String) onTimeSelected; + final ActivePrescriptionsResponseModel med; + + const ReminderTimerDialog({ + super.key, + required this.frequencyNumber, + required this.doseIndex, + required this.onTimeSelected, + required this.med, + }); @override State createState() => _ReminderTimerDialogState(); } class _ReminderTimerDialogState extends State { - final List options = ["Morning", "Afternoon", "Evening", "Midnight"]; - final List selectedTimes = ["Morning"]; // Default selection + TimeOfDay selectedTime = TimeOfDay.now(); + String? _selectedTime; + String bigTimeText = "00:00"; + bool showPicker = false; + final List> presetTimes = [ + ["06:00 AM", "07:00 AM", "08:00 AM", "09:00 AM"], // Morning + ["12:00 PM", "01:00 PM", "02:00 PM", "03:00 PM"], // Noon + ["06:00 PM", "07:00 PM", "08:00 PM", "09:00 PM"], // Evening + ]; @override Widget build(BuildContext context) { - return // - Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, - hasShadow: true,), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - // Checkboxes list - children: options.map((time) => buildCircleCheckbox(time)).toList(), - ).paddingAll(16), - ), - SizedBox(height: 25.h), - // Buttons Row - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close, color: AppColors.errorColor), - label: Text( - LocaleKeys.cancel.tr(), - style: TextStyle( - color: AppColors.errorColor, - fontWeight: FontWeight.w500, - fontSize: 14.fSize + final int bucket = widget.doseIndex.clamp(0, 2); + final List times = presetTimes[bucket]; + return Padding( + padding: const EdgeInsets.all(16), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bottomSheetBgColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + hasShadow: true, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Time for ${widget.doseIndex + 1} dose".needTranslation, + style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.bold), + ), + SizedBox(height: 12.h), + // Preset times + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + children: times.map((t) { + bool selected = _selectedTime == t; + return AppCustomChipWidget( + labelText: t, + backgroundColor: selected + ? AppColors.lightGreenButtonColor + : AppColors.transparent, + textColor: AppColors.textColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selected + ? AppColors.successColor + : AppColors.spacerLineColor, + width: 1.2, ), + borderRadius: BorderRadius.circular(12), ), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.secondaryLightRedColor, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 14), + onChipTap: () { + setState(() { + _selectedTime = t; + selectedTime = _parseTime(t); + bigTimeText = selectedTime.format(context).split(" ")[0]; + showPicker = false; + }); + }, + ); + }).toList(), + ), + SizedBox(height: 25.h), + GestureDetector( + onTap: () { + setState(() { + showPicker = !showPicker; + }); + }, + child: Center( + child: Column( + children: [ + Text( + bigTimeText, + style: TextStyle( + fontSize: 48.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor + ), ), - ), + Text( + selectedTime.period == DayPeriod.am ? "AM" : "PM", + style: TextStyle( + fontSize: 20.f, + fontWeight: FontWeight.bold, + color: AppColors.greyTextColor, + ), + ), + ], ), ), - SizedBox(width: 12.h), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - Navigator.pop(context, selectedTimes); + ), + SizedBox(height: 15.h), + // Time picker + if (showPicker) + SizedBox( + height: 100.h, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + use24hFormat: false, + initialDateTime: DateTime( + 2024, + 1, + 1, + selectedTime.hour, + selectedTime.minute, + ), + onDateTimeChanged: (DateTime newTime) { + setState(() { + _selectedTime = null; + selectedTime = TimeOfDay( + hour: newTime.hour, + minute: newTime.minute, + ); + bigTimeText = + selectedTime.format(context).split(" ")[0]; + }); }, - icon: const Icon(Icons.notifications_rounded), - label: Text( - LocaleKeys.setReminder.tr(), - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14.fSize + ), + ), + SizedBox(height: 25.h), + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.successColor, + foregroundColor: AppColors.whiteColor, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), - ), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.successColor, - foregroundColor: AppColors.whiteColor, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + onPressed: () async { + final selectedFormattedTime = + selectedTime.format(context); + widget.onTimeSelected(selectedFormattedTime); + try { + final parts = selectedFormattedTime.split(":"); + int hour = int.parse(parts[0]); + int minute = int.parse(parts[1].split(" ")[0]); + bool isPM = selectedFormattedTime.contains("PM"); + if (isPM && hour != 12) hour += 12; + if (!isPM && hour == 12) hour = 0; + int totalMinutes = hour * 60 + minute; + // Call setCalender() + await setCalender( + context, + eventId: widget.med.itemId.toString(), + selectedMinutes: totalMinutes, + frequencyNumber: widget.frequencyNumber, + days: widget.med.days ?? 1, + orderDate: widget.med.orderDate ?? "", + itemDescriptionN: widget.med.itemDescription ?? "", + route: widget.med.route ?? "", + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Reminder added to calendar ✅".needTranslation)), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text("Error while setting calendar: $e".needTranslation)), + ); + } + Navigator.pop(context); + }, + child: Text( + LocaleKeys.save.tr(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.f, + ), ), ), ), - ), - ], - ), - SizedBox(height: 30.h), - ], - ); - } - - Widget buildCircleCheckbox(String label) { - final bool isSelected = selectedTimes.contains(label); - return InkWell( - onTap: () { - setState(() { - if (isSelected) { - selectedTimes.remove(label); - } else { - selectedTimes.add(label); - } - }); - }, - borderRadius: BorderRadius.circular(25), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - children: [ - // Custom circle checkbox - Container( - width: 15.h, - height: 15.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: isSelected ? AppColors.spacerLineColor: AppColors.spacerLineColor, - width: 1.h, - ), - color: isSelected ? AppColors.errorColor: AppColors.transparent, - ), - ), - SizedBox(width: 12.h), - // Label text - Text( - label, - style: TextStyle(fontSize: 16.fSize, color: Colors.black87), + ], ), ], - ), + ).paddingAll(16), ), ); } - void showCircleCheckboxDialog(BuildContext context) async { - final selected = await showDialog>( - context: context, - builder: (context) => const ReminderTimerDialog(), - ); - - if (selected != null && selected.isNotEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Reminders set for: ${selected.join(', ').needTranslation}')), - ); + TimeOfDay _parseTime(String t) { + try { + int hour = int.parse(t.split(":")[0]); + int minute = int.parse(t.split(":")[1].split(" ")[0]); + bool pm = t.contains("PM"); + if (pm && hour != 12) hour += 12; + if (!pm && hour == 12) hour = 0; + return TimeOfDay(hour: hour, minute: minute); + } catch (e) { + return TimeOfDay.now(); } } } + + + diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index a48ca10..28a55ba 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -119,7 +119,7 @@ class TrackingScreen extends StatelessWidget { backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, text: "Share Your Live Locatin on Whatsapp".needTranslation, - fontSize: 12.fSize, + fontSize: 12.f, textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, onPressed: () {}, @@ -170,7 +170,7 @@ class TrackingScreen extends StatelessWidget { return Row( spacing: 16.h, children: [ - Utils.buildImgWithNetwork(url: "", iconColor: Colors.transparent) + Utils.buildImgWithNetwork(url: "",) .circle(52.h), Expanded( child: Column( @@ -244,7 +244,7 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: "Please wait for the call".needTranslation, style: TextStyle( - fontSize: 21.fSize, + fontSize: 21.f, fontWeight: FontWeight.w600, color: AppColors.textColor, ), @@ -252,7 +252,7 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: "...".needTranslation, style: TextStyle( - fontSize: 21.fSize, + fontSize: 21.f, fontWeight: FontWeight.w600, color: AppColors.errorColor, ), @@ -265,7 +265,7 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: "15:30".needTranslation, style: TextStyle( - fontSize: 21.fSize, + fontSize: 21.f, fontWeight: FontWeight.w600, color: AppColors.textColor, ), @@ -273,7 +273,7 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: " mins ".needTranslation, style: TextStyle( - fontSize: 21.fSize, + fontSize: 21.f, fontWeight: FontWeight.w600, color: AppColors.errorColor, ), @@ -281,7 +281,7 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: "to hospital".needTranslation, style: TextStyle( - fontSize: 21.fSize, + fontSize: 21.f, fontWeight: FontWeight.w600, color: AppColors.textColor, ), diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index 3dc2aa1..6a8fdbc 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -49,7 +49,7 @@ class NearestERItem extends StatelessWidget { ).toShimmer2(isShow: isLoading) : Utils.buildImgWithNetwork( url: nearestERItem.projectImageURL ?? '', - iconColor: Colors.transparent, + // iconColor: Colors.transparent, ).circle(24.h).toShimmer2(isShow: isLoading), const SizedBox(width: 12), Expanded( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 2f72227..0e9eb30 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -351,7 +351,16 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Quick Links".needTranslation.toText16(isBold: true), + CustomButton(text: "Quick Links".needTranslation, + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: ActiveMedicationPage(), + ), + ); + }, + ), + // "Quick Links".needTranslation.toText16(isBold: true), Row( children: [ "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index ce2f87c..3aab1de 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -77,5 +77,7 @@ static const Color calenderTextColor = Color(0xFFD0D0D0); static const Color lightGreenButtonColor = Color(0x2618C273); static const Color lightRedButtonColor = Color(0x1AED1C2B); +static const Color lightGreyTextColor = Color(0xFF959595); +static const Color labelColorYellow = Color(0xFFFBCB6E); } From 77242825bbfd2653b310de8836409303d1fd91c3 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 18 Nov 2025 10:54:57 +0300 Subject: [PATCH 04/46] fix toggle --- .../active_prescriptions_view_model.dart | 168 +--- .../active_prescriptions_response_model.dart | 2 +- .../active_medication_page.dart | 730 ++++++++++++------ 3 files changed, 512 insertions(+), 388 deletions(-) diff --git a/lib/features/active_prescriptions/active_prescriptions_view_model.dart b/lib/features/active_prescriptions/active_prescriptions_view_model.dart index 03f84ea..e4da04d 100644 --- a/lib/features/active_prescriptions/active_prescriptions_view_model.dart +++ b/lib/features/active_prescriptions/active_prescriptions_view_model.dart @@ -5,36 +5,24 @@ import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescri import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class ActivePrescriptionsViewModel extends ChangeNotifier { - bool isActivePrescriptionsDetailsLoading = false; - late ActivePrescriptionsRepo activePrescriptionsRepo; late ErrorHandlerService errorHandlerService; + List activePrescriptionsDetailsList = []; ActivePrescriptionsViewModel({ required this.activePrescriptionsRepo, required this.errorHandlerService, }); - List activePrescriptionsDetailsList = []; - - initActivePrescriptionsViewModel() { - getActiveMedications(); - notifyListeners(); - } - - setPrescriptionsDetailsLoading() { - isActivePrescriptionsDetailsLoading = true; - notifyListeners(); - } - - // Get medications list Future getActiveMedications({ Function(dynamic)? onSuccess, Function(String)? onError, }) async { - final result = await activePrescriptionsRepo.getActivePrescriptionsDetails(); + final result = + await activePrescriptionsRepo.getActivePrescriptionsDetails(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async => + await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 1) { activePrescriptionsDetailsList = apiResponse.data ?? []; @@ -56,140 +44,58 @@ class ActivePrescriptionsViewModel extends ChangeNotifier { return DateTime.tryParse(date) ?? DateTime.now(); } - // Extract numeric value ( "3 / week" → 3) - int extractNumberFromFrequency(String? frequency) { - if (frequency == null) return 1; - final m = RegExp(r'(\d+)').firstMatch(frequency); - if (m != null) return int.tryParse(m.group(1)!) ?? 1; - return 1; - } - - // Generate medication days based on frequency text List generateMedicationDays(ActivePrescriptionsResponseModel med) { final start = parseDate(med.startDate); final duration = med.days ?? 0; - final frequency = (med.frequency ?? "").toLowerCase().trim(); - - List result = []; - if (duration <= 0) return result; - - // Every N hours ( "Every Six Hours", "Every 8 hours") - if (frequency.contains("hour")) { - final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); - int intervalHours = 0; - - if (match != null) { - intervalHours = int.tryParse(match.group(1)!) ?? 0; - } else { - // handle text numbers like "Every six hours" - final textNum = { - "one": 1, - "two": 2, - "three": 3, - "four": 4, - "five": 5, - "six": 6, - "seven": 7, - "eight": 8, - "nine": 9, - "ten": 10, - "twelve": 12, - }; - for (var key in textNum.keys) { - if (frequency.contains(key)) { - intervalHours = textNum[key]!; - break; - } - } - } - - if (intervalHours > 0) { - for (int day = 0; day < duration; day++) { - final dayStart = start.add(Duration(days: day)); - for (int hour = 0; hour < 24; hour += intervalHours) { - result.add(DateTime(dayStart.year, dayStart.month, dayStart.day, hour)); - } - } - return result; - } - } - - // Daily (every day) - if (frequency.contains("day") && - !frequency.contains("every other") && - !frequency.contains("every ")) { - for (int i = 0; i < duration; i++) { - result.add(start.add(Duration(days: i))); - } - } - - // Every other day - else if (frequency.contains("every other day")) { - for (int i = 0; i < duration; i += 2) { - result.add(start.add(Duration(days: i))); - } + if (duration <= 0) return []; + final f = (med.frequency ?? "").toLowerCase().trim(); + int intervalDays = 1; + + if (f.contains("every six hours") || + f.contains("every 6 hours") || + f.contains("every four hours") || + f.contains("every 4 hours") || + f.contains("every eight hours") || + f.contains("every 8 hours") || + f.contains("every 12 hours") || + f.contains("every twelve hours") || + f.contains("every 24 hours") || + f.contains("3 times a day") || + f.contains("once a day")) { + intervalDays = 1; } - - // Every N days → e.g. "Every 3 days", "Every 5 days" - else if (frequency.contains("every") && frequency.contains("day")) { - final match = RegExp(r'every\s+(\d+)').firstMatch(frequency); - final interval = match != null ? int.tryParse(match.group(1)!) ?? 1 : 1; - for (int i = 0; i < duration; i += interval) { - result.add(start.add(Duration(days: i))); - } + else if (f.contains("once a week")) { + intervalDays = 7; } - - // Once or twice a week - else if (frequency.contains("once a week")) { - for (int i = 0; i < duration; i += 7) { - result.add(start.add(Duration(days: i))); - } - } else if (frequency.contains("twice a week")) { - for (int i = 0; i < duration; i += 3) { - result.add(start.add(Duration(days: i))); - } + else if (f.contains("every 3 days")) { + intervalDays = 3; } - - // Numeric frequency like "3 / week", "2 / week" - else if (frequency.contains("week")) { - int timesPerWeek = extractNumberFromFrequency(frequency); - double interval = 7 / timesPerWeek; - double dayPointer = 0; - - for (int i = 0; i < duration; i++) { - if (i >= dayPointer.floor()) { - result.add(start.add(Duration(days: i))); - dayPointer += interval; - } - } + else if (f.contains("every other day")) { + intervalDays = 2; } - else { - result.add(start); - } - - - final unique = {}; - for (final d in result) { - unique["${d.year}-${d.month}-${d.day}"] = d; + List result = []; + for (int offset = 0; offset < duration; offset += intervalDays) { + result.add(start.add(Duration(days: offset))); } - return unique.values.toList()..sort((a, b) => a.compareTo(b)); + return result; } - bool sameYMD(DateTime a, DateTime b) => a.year == b.year && a.month == b.month && a.day == b.day; - // Filter medications for selected day - List getMedsForSelectedDay(DateTime selectedDate) { - final target = DateTime(selectedDate.year, selectedDate.month, selectedDate.day); + List getMedsForSelectedDay( + DateTime selectedDate) { + final clean = DateTime(selectedDate.year, selectedDate.month, selectedDate.day); + return activePrescriptionsDetailsList.where((med) { final days = generateMedicationDays(med); - return days.any((d) => sameYMD(d, target)); + return days.any((d) => sameYMD(d, clean)); }).toList(); } } + diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart index eb216a6..42faafa 100644 --- a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -35,7 +35,7 @@ class ActivePrescriptionsResponseModel { int? scaleOffset; String? startDate; - // ✅ Added for reminder feature + // Added for reminder feature List selectedDoseTimes = []; bool isReminderOn = false; // toggle status diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index aa2abe5..d27b35d 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -18,6 +18,7 @@ import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; // for date formatting import 'package:provider/provider.dart'; import '../../widgets/loader/bottomsheet_loader.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class ActiveMedicationPage extends StatefulWidget { @@ -32,43 +33,98 @@ class _ActiveMedicationPageState extends State { late DateTime selectedDate; List selectedDayMeds = []; ActivePrescriptionsViewModel? activePreVM; - Map medReminderStatus = {}; + + + Map medReminderStatus = {}; + + String _buildMedKey(ActivePrescriptionsResponseModel med) { + return "${med.itemId}_${med.startDate}_${med.days}_${med.frequency}"; + } + + int _getDosesCount(ActivePrescriptionsResponseModel med) { + return med.frequencyNumber ?? 1; + } @override - void initState() { + void initState() { super.initState(); currentDate = DateTime.now(); selectedDate = currentDate; + WidgetsBinding.instance.addPostFrameCallback((_) async { - activePreVM = Provider.of(context, listen: false); + activePreVM = + Provider.of(context, listen: false); LoaderBottomSheet.showLoader(); await activePreVM!.getActiveMedications( - onSuccess: (_) { + onSuccess: (_) async { LoaderBottomSheet.hideLoader(); - final todayMeds = activePreVM!.getMedsForSelectedDay(selectedDate); - setState(() { - selectedDayMeds = todayMeds; + + final todayMeds = + activePreVM!.getMedsForSelectedDay(selectedDate); + setState(() => selectedDayMeds = todayMeds); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await loadSavedReminders(); }); }, onError: (_) { LoaderBottomSheet.hideLoader(); }, ); - activePreVM!.addListener(() { + + activePreVM!.addListener(() { if (!mounted) return; - final medsForDay = activePreVM!.getMedsForSelectedDay(selectedDate); + final medsForDay = + activePreVM!.getMedsForSelectedDay(selectedDate); setState(() => selectedDayMeds = medsForDay); }); }); } + Future loadSavedReminders() async { + final prefs = await SharedPreferences.getInstance(); + + for (final med in activePreVM!.activePrescriptionsDetailsList) { + final medKey = _buildMedKey(med); + final doses = _getDosesCount(med); + + med.selectedDoseTimes = + List.filled(doses, null, growable: false); + + for (int i = 0; i < doses; i++) { + final saved = prefs.getString("doseTime_${medKey}_$i"); + if (saved != null) { + med.selectedDoseTimes[i] = saved; + } + } + + final reminderOn = + prefs.getBool("reminderStatus_$medKey") ?? false; + med.isReminderOn = reminderOn; + medReminderStatus[medKey] = reminderOn; + } + + setState(() {}); + } + + Future saveReminderStatus(String medKey, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool("reminderStatus_$medKey", value); + } + + Future saveDoseTime( + String medKey, int doseIndex, String time) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString("doseTime_${medKey}_$doseIndex", time); + } + + List getUpcomingDays() => + List.generate(7, (index) => currentDate.add(Duration(days: index))); - List getUpcomingDays() => List.generate(7, (index) => currentDate.add(Duration(days: index))); @override Widget build(BuildContext context) { final days = getUpcomingDays(); - final dateText = "${selectedDate.day}${getSuffix(selectedDate.day)} ${DateFormat.MMMM().format(selectedDate)}"; - return Scaffold( + return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( onBackPressed: () => Navigator.of(context).pop(), @@ -99,13 +155,13 @@ class _ActiveMedicationPageState extends State { }, ), ), - SizedBox(height: 20.h), + SizedBox(height: 20.h), RichText( text: TextSpan( children: [ TextSpan( text: "${selectedDate.day}", - style: TextStyle( + style: TextStyle( color: AppColors.textColor, fontSize: 16, fontWeight: FontWeight.w500, @@ -115,7 +171,7 @@ class _ActiveMedicationPageState extends State { child: Transform.translate( offset: const Offset(0, -4), child: Text( - getSuffix(selectedDate.day), + _getSuffix(selectedDate.day), style: const TextStyle( fontSize: 12, color: AppColors.textColor, @@ -135,121 +191,163 @@ class _ActiveMedicationPageState extends State { ], ), ), - Text("Medications".needTranslation, + Text("Medications".needTranslation, style: TextStyle( color: AppColors.primaryRedBorderColor, fontSize: 12.f, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), + SizedBox(height: 16.h), Expanded( child: SingleChildScrollView( - child: selectedDayMeds.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: selectedDayMeds.length, - itemBuilder: (context, index) { - final med = selectedDayMeds[index]; - final doses = med.doseDailyQuantity ?? 1; - med.selectedDoseTimes ??= List.filled(doses, null); - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - margin: EdgeInsets.all(10), - child: Column( - children: [ - _buildMedHeader(med), - Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: selectedDayMeds.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: selectedDayMeds.length, + itemBuilder: (context, index) { + final med = selectedDayMeds[index]; + final doses = _getDosesCount(med); + if (med.selectedDoseTimes.length != doses) { + final old = med.selectedDoseTimes; + med.selectedDoseTimes = + List.filled(doses, null, + growable: false); + for (int i = 0; + i < old.length && i < doses; + i++) { + med.selectedDoseTimes[i] = old[i]; + } + } + + return Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + margin: EdgeInsets.all(10), + child: Column( + children: [ + _buildMedHeader(med), + Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + // Utils.buildSvgWithAssets( + // icon: AppAssets., + // height: 18.h, + // width: 18.h, + // iconColor: + // AppColors.lightGreyTextColor, + // ), + Icon( + Icons.info_outline, + color: AppColors + .lightGreyTextColor, + size: 18, + ), + SizedBox(width: 6.h), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Remarks: " + .needTranslation, + style: TextStyle( + color: + AppColors.textColor, + fontWeight: + FontWeight.w600, + fontSize: 10, + ), + ), + TextSpan( + text: + "some remarks about the prescription will be here" + .needTranslation, + style: TextStyle( + color: AppColors + .lightGreyTextColor, + fontWeight: + FontWeight.normal, + fontSize: 10, + ), + ), + ], + ), + ), + ), + ], + ).paddingOnly(left: 16, right: 16), + const Divider( + color: AppColors.greyColor), + GestureDetector( + onTap: () => showDoseDialog(med), + child: Row( children: [ - Icon( - Icons.info_outline, - color: AppColors.lightGreyTextColor, - size: 20, + Container( + width: 40.h, + height: 40.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: + BorderRadius.circular(10), + ), + child: + Utils.buildSvgWithAssets( + icon: AppAssets.bell, + height: 24.h, + width: 24.h, + iconColor: + AppColors.greyTextColor, + ), ), - SizedBox(width: 6.h), + SizedBox(width: 12.h), Expanded( - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: "Remarks: ".needTranslation, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Set Reminder" + .needTranslation, style: TextStyle( - color: AppColors.textColor, - fontWeight: FontWeight.w600, - fontSize: 10, - ), - ), - TextSpan( - text: "some remarks about the prescription will be here".needTranslation, + fontSize: 14.f, + fontWeight: + FontWeight.w600, + color: AppColors + .textColor)), + Text( + "Notify me before the consumption time" + .needTranslation, style: TextStyle( - color: AppColors.lightGreyTextColor, - fontWeight: FontWeight.normal, - fontSize: 10, - ), - ), - ], - ), + fontSize: 12.f, + color: AppColors + .textColorLight, + )), + ], ), ), + _buildToggle(med), ], - ).paddingOnly(left: 16, right: 16), - const Divider(color: AppColors.greyColor), - // Reminder Section - GestureDetector( - onTap: () => showDoseDialog(med, index), - child: Row( - children: [ - Container( - width: 40.h, - height: 40.h, - alignment: Alignment.center, - decoration: BoxDecoration( - color: AppColors.greyColor, - borderRadius: BorderRadius.circular(10), - ), - child: Utils.buildSvgWithAssets( - icon: AppAssets.bell, - height: 24.h, - width: 24.h, - iconColor: AppColors.greyTextColor, - ), - ), - SizedBox(width: 12.h), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text("Set Reminder".needTranslation, - style: TextStyle( - fontSize: 14.f, - fontWeight: FontWeight.w600, - color: AppColors.textColor)), - Text("Notify me before the consumption time".needTranslation, - style: TextStyle( - fontSize: 12.f, - color: AppColors.textColorLight, - )), - ], - ), - ), - _buildToggle(index) - ], - ).paddingAll(16), - ), - const Divider(color: AppColors.greyColor), - _buildButtons(), - ], - ), - ); - }, - ) - : Utils.getNoDataWidget(context, - noDataText: "No medications today".needTranslation), + ).paddingAll(16), + ), + const Divider( + color: AppColors.greyColor), + _buildButtons(), + ], + ), + ); + }, + ) + : Utils.getNoDataWidget( + context, + noDataText: + "No medications today".needTranslation, + ), ), ), ], @@ -257,55 +355,67 @@ class _ActiveMedicationPageState extends State { ); } - //medicine card - Widget _buildMedHeader(ActivePrescriptionsResponseModel med) => Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Container( - width: 59.h, - height: 59.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.spacerLineColor, - borderRadius: 30.r, - hasShadow: false, + // medicine card + Widget _buildMedHeader(ActivePrescriptionsResponseModel med) => + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Container( + width: 59.h, + height: 59.h, + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.spacerLineColor, + borderRadius: 30.r, + hasShadow: false, + ), + child: Utils.buildImgWithNetwork( + url: med.productImageString ?? "", + iconColor: Colors.transparent) + .circle(52.h)), + ), + SizedBox(width: 12.h), + Expanded( + child: Text( + med.itemDescription ?? "", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor), ), - child: Utils.buildImgWithNetwork( - url: med.productImageString ?? "" ).circle(52.h) - ), - ), - SizedBox(width: 12.h), - Expanded( - child: Text( - med.itemDescription ?? "", - style: TextStyle( - fontSize: 16.f, - fontWeight: FontWeight.w600, - color: AppColors.textColor), + ), + ]), + SizedBox(height: 12.h), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + AppCustomChipWidget( + labelText: + "Route: ${med.route}".needTranslation), + AppCustomChipWidget( + labelText: + "Frequency: ${med.frequency}".needTranslation), + AppCustomChipWidget( + labelText: + "Daily Dose: ${med.doseDailyQuantity}" + .needTranslation), + AppCustomChipWidget( + labelText: + "Duration: ${med.days}".needTranslation), + ], ), - ), - ]), - SizedBox(height: 12.h), - Wrap( - spacing: 4, - runSpacing: 4, - children: [ - AppCustomChipWidget(labelText: "Route: ${med.route}".needTranslation), - AppCustomChipWidget(labelText: "Frequency: ${med.frequency}".needTranslation), - AppCustomChipWidget(labelText: "Daily Dose: ${med.doseDailyQuantity}".needTranslation), - AppCustomChipWidget(labelText: "Duration: ${med.days}".needTranslation), ], ), - ], - ), - ); + ); Widget _buildButtons() => Padding( - padding: EdgeInsets.all(16), + padding: EdgeInsets.all(16), child: Row(children: [ Expanded( child: CustomButton( @@ -317,24 +427,28 @@ class _ActiveMedicationPageState extends State { textColor: AppColors.errorColor, ), ), - SizedBox(width: 12.h), + SizedBox(width: 12.h), Expanded( child: CustomButton( - text: "Read Instructions".needTranslation, fontSize: 13.f, onPressed: () {})), + text: "Read Instructions".needTranslation, + fontSize: 13.f, + onPressed: () {})), ]), ); - Widget _buildToggle(int index) { - final value = medReminderStatus[index] ?? false; + Widget _buildToggle(ActivePrescriptionsResponseModel med) { + final medKey = _buildMedKey(med); + final value = medReminderStatus[medKey] ?? false; return GestureDetector( onTap: () async { - await showDoseDialog(selectedDayMeds[index], index); - setState(() { - if ((selectedDayMeds[index].selectedDoseTimes ?? []).any((t) => t != null)) { - medReminderStatus[index] = true; - } - }); + await showDoseDialog(med); + final hasTime = + (med.selectedDoseTimes).any((t) => t != null); + medReminderStatus[medKey] = hasTime; + await saveReminderStatus(medKey, hasTime); + + setState(() {}); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), @@ -342,11 +456,14 @@ class _ActiveMedicationPageState extends State { height: 28.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: value ? AppColors.lightGreenColor : AppColors.greyColor.withOpacity(0.3), + color: value + ? AppColors.lightGreenColor + : AppColors.greyColor.withOpacity(0.3), ), child: AnimatedAlign( duration: const Duration(milliseconds: 200), - alignment: value ? Alignment.centerRight : Alignment.centerLeft, + alignment: + value ? Alignment.centerRight : Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(3), child: Container( @@ -354,7 +471,9 @@ class _ActiveMedicationPageState extends State { height: 22.h, decoration: BoxDecoration( shape: BoxShape.circle, - color: value ? AppColors.textGreenColor : AppColors.greyTextColor, + color: value + ? AppColors.textGreenColor + : AppColors.greyTextColor, ), ), ), @@ -363,10 +482,16 @@ class _ActiveMedicationPageState extends State { ); } - Future showDoseDialog(ActivePrescriptionsResponseModel med, int medIndex) { - final doses = med.frequencyNumber ?? 1; + + Future showDoseDialog(ActivePrescriptionsResponseModel med) { + final doses = _getDosesCount(med); if (med.selectedDoseTimes.length != doses) { - med.selectedDoseTimes = List.generate(doses, (_) => null); + final old = med.selectedDoseTimes; + med.selectedDoseTimes = + List.filled(doses, null, growable: false); + for (int i = 0; i < old.length && i < doses; i++) { + med.selectedDoseTimes[i] = old[i]; + } } return showModalBottomSheet( @@ -378,19 +503,22 @@ class _ActiveMedicationPageState extends State { height: 520.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bottomSheetBgColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + customBorder: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), hasShadow: true, ), - child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Text( + Text( "Reminders".needTranslation, style: TextStyle( fontSize: 20.f, @@ -400,11 +528,17 @@ class _ActiveMedicationPageState extends State { ), GestureDetector( onTap: () => Navigator.pop(context), - child: Icon(Icons.close, color:AppColors.blackBgColor), + child: Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + height: 24.h, + width: 24.h, + iconColor: + AppColors.blackBgColor, + ), ), ], ), - SizedBox(height: 20.h), + SizedBox(height: 20.h), Expanded( child: ListView.builder( itemCount: doses, @@ -415,64 +549,91 @@ class _ActiveMedicationPageState extends State { AppColors.labelColorYellow, AppColors.purpleBg ][doseIndex % 4]; - - final doseLabel = "${doseIndex + 1}${getSuffix(doseIndex + 1)}"; - final time = med.selectedDoseTimes[doseIndex] ?? "Not set yet"; - + final doseLabel = + "${doseIndex + 1}${_getSuffix(doseIndex + 1)}"; + final time = + med.selectedDoseTimes[doseIndex] ?? + "Not set yet"; return GestureDetector( onTap: () { Navigator.pop(context); - showTimePickerSheet(med, medIndex, doseIndex); + showTimePickerSheet(med, doseIndex); }, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 16.r, hasShadow: false, ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.symmetric( - vertical: 6, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 14), decoration: BoxDecoration( color: badgeColor, borderRadius: BorderRadius.circular(12), ), - child: Text( - doseLabel, - style: TextStyle( - color: AppColors.whiteColor, - fontWeight: FontWeight.bold, - fontSize: 16.f, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "${doseIndex + 1}", + style: TextStyle( + color: AppColors.whiteColor, + fontWeight: FontWeight.bold, + fontSize: 16.f, + ), + ), + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -4), + child: Text( + _getSuffix(doseIndex + 1), + style: TextStyle( + color: AppColors.whiteColor, + fontSize: 10.f, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], ), ), ), SizedBox(height: 8.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( "Set reminder for $doseLabel dose", - style: TextStyle( + style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.bold, fontSize: 16.f, ), ), ), - Icon(Icons.arrow_forward_outlined, - size: 24.w, color: AppColors.textColor), + Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + height: 24.h, + width: 24.h, + iconColor: + AppColors.textColor, + ), ], ), - SizedBox(height: 4.h), + SizedBox(height: 4.h), Text( time, - style: TextStyle( + style: TextStyle( fontSize: 12.f, color: AppColors.greyTextColor, fontWeight: FontWeight.w500, @@ -494,7 +655,7 @@ class _ActiveMedicationPageState extends State { void showTimePickerSheet( - ActivePrescriptionsResponseModel med, int medIndex, int doseIndex) { + ActivePrescriptionsResponseModel med, int doseIndex) { showModalBottomSheet( context: context, isScrollControlled: true, @@ -502,20 +663,24 @@ class _ActiveMedicationPageState extends State { builder: (_) => Container( width: double.infinity, height: 460.h, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: AppColors.bottomSheetBgColor, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), ), child: ReminderTimerDialog( med: med, - frequencyNumber: med.doseDailyQuantity ?? 1, + frequencyNumber: _getDosesCount(med), doseIndex: doseIndex, - onTimeSelected: (String time) { - setState(() { - med.selectedDoseTimes[doseIndex] = time; - medReminderStatus[medIndex] = true; - }); + onTimeSelected: (String time) async { + final medKey = _buildMedKey(med); + med.selectedDoseTimes[doseIndex] = time; + await saveDoseTime(medKey, doseIndex, time); + medReminderStatus[medKey] = true; + await saveReminderStatus(medKey, true); + setState(() {}); }, ), ), @@ -529,7 +694,8 @@ class _ActiveMedicationPageState extends State { return GestureDetector( onTap: () { final vm = - Provider.of(context, listen: false); + Provider.of(context, + listen: false); setState(() { selectedDate = date; selectedDayMeds = vm.getMedsForSelectedDay(date); @@ -544,10 +710,11 @@ class _ActiveMedicationPageState extends State { ? AppColors.secondaryLightRedBorderColor : Colors.transparent, border: Border.all( - color: isSelected - ? AppColors.primaryRedBorderColor - : AppColors.spacerLineColor, - width: 1), + color: isSelected + ? AppColors.primaryRedBorderColor + : AppColors.spacerLineColor, + width: 1, + ), ), child: Padding( padding: const EdgeInsets.all(8.0), @@ -563,7 +730,7 @@ class _ActiveMedicationPageState extends State { fontSize: 11.f, fontWeight: FontWeight.w500), ), - SizedBox(height: 5.h), + SizedBox(height: 5.h), Text("${date.day}", style: TextStyle( fontSize: 16.f, @@ -577,14 +744,14 @@ class _ActiveMedicationPageState extends State { ); } - String getSuffix(int day) { + String _getSuffix(int day) { if (day == 1 || day == 21 || day == 31) return "st"; if (day == 2 || day == 22) return "nd"; if (day == 3 || day == 23) return "rd"; return "th"; } -} +} class ReminderTimerDialog extends StatefulWidget { @@ -617,32 +784,94 @@ class _ReminderTimerDialogState extends State { ["06:00 PM", "07:00 PM", "08:00 PM", "09:00 PM"], // Evening ]; + String _getSuffix(int number) { + if (number == 1 || number == 21 || number == 31) return "st"; + if (number == 2 || number == 22) return "nd"; + if (number == 3 || number == 23) return "rd"; + return "th"; + } + @override Widget build(BuildContext context) { final int bucket = widget.doseIndex.clamp(0, 2); final List times = presetTimes[bucket]; + return Padding( padding: const EdgeInsets.all(16), child: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bottomSheetBgColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + customBorder: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), hasShadow: true, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Time for ${widget.doseIndex + 1} dose".needTranslation, - style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.bold), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Time for ", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + TextSpan( + text: "${widget.doseIndex + 1}", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + WidgetSpan( + child: Transform.translate( + offset: const Offset(0, -6), + child: Text( + _getSuffix(widget.doseIndex + 1), + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + ), + ), + TextSpan( + text: " reminder", + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child:Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + height: 24.h, + width: 24.h, + iconColor: + AppColors.blackBgColor, + ),), + ], ), - SizedBox(height: 12.h), - // Preset times + SizedBox(height: 12.h), Wrap( spacing: 8, runSpacing: 8, - alignment: WrapAlignment.start, children: times.map((t) { bool selected = _selectedTime == t; return AppCustomChipWidget( @@ -660,7 +889,7 @@ class _ReminderTimerDialogState extends State { ), borderRadius: BorderRadius.circular(12), ), - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), onChipTap: () { setState(() { _selectedTime = t; @@ -672,27 +901,25 @@ class _ReminderTimerDialogState extends State { ); }).toList(), ), - SizedBox(height: 25.h), + SizedBox(height: 25.h), GestureDetector( onTap: () { - setState(() { - showPicker = !showPicker; - }); + setState(() => showPicker = !showPicker); }, child: Center( child: Column( children: [ Text( bigTimeText, - style: TextStyle( + style: TextStyle( fontSize: 48.f, fontWeight: FontWeight.bold, - color: AppColors.textColor + color: AppColors.textColor, ), ), Text( selectedTime.period == DayPeriod.am ? "AM" : "PM", - style: TextStyle( + style: TextStyle( fontSize: 20.f, fontWeight: FontWeight.bold, color: AppColors.greyTextColor, @@ -702,8 +929,7 @@ class _ReminderTimerDialogState extends State { ), ), ), - SizedBox(height: 15.h), - // Time picker + SizedBox(height: 15.h), if (showPicker) SizedBox( height: 100.h, @@ -717,20 +943,16 @@ class _ReminderTimerDialogState extends State { selectedTime.hour, selectedTime.minute, ), - onDateTimeChanged: (DateTime newTime) { + onDateTimeChanged: (newTime) { setState(() { _selectedTime = null; - selectedTime = TimeOfDay( - hour: newTime.hour, - minute: newTime.minute, - ); - bigTimeText = - selectedTime.format(context).split(" ")[0]; + selectedTime = TimeOfDay(hour: newTime.hour, minute: newTime.minute); + bigTimeText = selectedTime.format(context).split(" ")[0]; }); }, ), ), - SizedBox(height: 25.h), + SizedBox(height: 25.h), Row( children: [ Expanded( @@ -745,8 +967,7 @@ class _ReminderTimerDialogState extends State { ), ), onPressed: () async { - final selectedFormattedTime = - selectedTime.format(context); + final selectedFormattedTime = selectedTime.format(context); widget.onTimeSelected(selectedFormattedTime); try { final parts = selectedFormattedTime.split(":"); @@ -756,7 +977,6 @@ class _ReminderTimerDialogState extends State { if (isPM && hour != 12) hour += 12; if (!isPM && hour == 12) hour = 0; int totalMinutes = hour * 60 + minute; - // Call setCalender() await setCalender( context, eventId: widget.med.itemId.toString(), @@ -768,21 +988,18 @@ class _ReminderTimerDialogState extends State { route: widget.med.route ?? "", ); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Reminder added to calendar ✅".needTranslation)), + SnackBar(content: Text("Reminder added to calendar ✅".needTranslation)), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text("Error while setting calendar: $e".needTranslation)), + SnackBar(content: Text("Error while setting calendar: $e".needTranslation)), ); } Navigator.pop(context); }, child: Text( LocaleKeys.save.tr(), - style: TextStyle( + style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16.f, ), @@ -797,7 +1014,6 @@ class _ReminderTimerDialogState extends State { ); } - TimeOfDay _parseTime(String t) { try { int hour = int.parse(t.split(":")[0]); @@ -806,7 +1022,7 @@ class _ReminderTimerDialogState extends State { if (pm && hour != 12) hour += 12; if (!pm && hour == 12) hour = 0; return TimeOfDay(hour: hour, minute: minute); - } catch (e) { + } catch (_) { return TimeOfDay.now(); } } @@ -815,3 +1031,5 @@ class _ReminderTimerDialogState extends State { + + From e17d3cde87a0fe0a2060b8c98531ca0c8eff074b Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 18 Nov 2025 11:19:15 +0300 Subject: [PATCH 05/46] fix toggle --- lib/presentation/emergency_services/widgets/nearestERItem.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index 6a8fdbc..3dc2aa1 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -49,7 +49,7 @@ class NearestERItem extends StatelessWidget { ).toShimmer2(isShow: isLoading) : Utils.buildImgWithNetwork( url: nearestERItem.projectImageURL ?? '', - // iconColor: Colors.transparent, + iconColor: Colors.transparent, ).circle(24.h).toShimmer2(isShow: isLoading), const SizedBox(width: 12), Expanded( From 0b3fea230f75bcda90ded25e29d3a93cf9adf610 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Mon, 15 Dec 2025 10:28:48 +0300 Subject: [PATCH 06/46] monthly report --- assets/images/jpg/report.jpg | Bin 0 -> 37509 bytes lib/core/api_consts.dart | 1 + lib/core/app_assets.dart | 4 + lib/core/dependencies.dart | 6 + .../active_prescriptions_response_model.dart | 205 ++++--------- .../hmg_services_component_model.dart | 2 + .../terms_conditions_repo.dart | 60 ++++ .../terms_conditions_view_model.dart | 45 +++ lib/main.dart | 6 +- .../active_medication_page.dart | 2 +- .../hmg_services/services_page.dart | 25 +- .../hmg_services/services_view.dart | 21 +- .../monthly_reports/monthly_reports_page.dart | 283 ++++++++++++++++++ .../monthly_reports/user_agreement_page.dart | 117 ++++++++ lib/routes/app_routes.dart | 7 +- pubspec.yaml | 2 +- 16 files changed, 632 insertions(+), 154 deletions(-) create mode 100644 assets/images/jpg/report.jpg create mode 100644 lib/features/terms_conditions/terms_conditions_repo.dart create mode 100644 lib/features/terms_conditions/terms_conditions_view_model.dart create mode 100644 lib/presentation/monthly_reports/monthly_reports_page.dart create mode 100644 lib/presentation/monthly_reports/user_agreement_page.dart diff --git a/assets/images/jpg/report.jpg b/assets/images/jpg/report.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5846cd538dec27f12011b3de3ecfbdae4d411f29 GIT binary patch literal 37509 zcmdpdWmH^EwrJx{Xj}s{(6}_N!QI^hbT{tq3GVJ5f+e_X2*I_H;GWgVj{Z0%)5 z=jZI?;wkDU0s0fU=wtnlW^NGOpIy8hB|y@DP^B|c*P@ek^{}QB;1cA7@dygh35jq) z1cZe6g*fPVc_0GZJbc`IJe<6IqWnCf{JeC3FQ7+i9&j5`9XW-+(|UZ80R3GkUteD? zUp_8Z4_j`Ch=>R`4=*<_FXv+qPEUUqFDpMz7f*)2FvwYZ!aVHVyzE_F=>EWH1$FiI zk^nt&`Zp1r-Ts00-|Fh0I;yMx&t09J|4_i6Xg$4jtpAJOe~Z{t&)?0OTgTed)!PGR z{n(x14`4S@Sr2O~FINveS68RMw5Vn0>gDQb=jui$EBmL`=vWNxUEr?1o~(busH=-A zyLft8xxlQI>uXf?Dj}m&f3G?#~QBS;p$BHr`V$Q|B)A2`TvOT@38Ry z$cwDPe}v_JWQO~X!TqPf{;TQHg8r!f!*m}X{$c#qE{_)P@o3r)X8`;^4Hf|Jef)%w z9@YR60HptYLH@6T@{jU&aAYK;$G4Baf4unLRvvl*Kun}oBy(K9oMb#l^zJK*7X$tRtbK z0f2-===8)G5Re{;blR+LvscEvfPN;aHZOx``drI`fmP~9ovcMna`#XfK1LrhrdGk% zjeQU-(2{=HQu!f;HY}k>=8yr8JRl=uprfJv!4(n^nVt{@6(X%|MdS`b3*}9f z`Su72B_{c(;}OQdw{ipNiwr~o0we*yfAgbA{a;_`%n~F6Ik&7x zOen+q$OCb{%YI1;!f#x`A^qKv=sC594v=Uu8u41z$hqW5OTN>TS?2XwEY9 z-X2D0Jt6Rt3@J{4f_xKEUn>qi%^rQOAwYUsw^M_rtlzC+kYU(rQ^c~OEPRwT=Y!0IL^O)r8V7$Yfys-95cw{p6!uYx0mr_qScft;5|{M zD5;D#n&b(wl(JyG>E~C_Oi+up&5Vw*ajhyNLKyC1ZnNqSyGB*m;_(Nz7n9-2*3xl8knkM(k+PH z6Nu&dleVY@PO86pSmI&Qa?yBHSLkLw>d{rs%R)%z+xQ<3`W2 z9_^mPvaTXm>bEIG7#sVOh!VLJFRP%W{?c($)d*48Z9b$tK{i?2JM^BD&Mc2pHy}?e zL835AHL*x{Yq|;8%~9+KL8}-UHPN|;+#WV8%uSg9v8&a@u&z5))lESNdj^OYX=)+ z6Xylqw;fzscPT5Zv7xMZs8ncmsTlBRP779-+K;1K+=Jr|#)kNy!V^bFmCf8sUSeJs zLeB4tV?S(OtT`i9Z+G)(u=~7`aH}gk3C>!k_F=+t4iG-6 zpqm*4zrA#}7_rWM(lQ{FUu;YSq^88=WWQqe@kpz=QJgnh6{`K5^q$asy%lX9&CL2t zYF*z}Fm8QyiwFHMHr`BYP%R&DqoSA{~T!H0x;&z^c(7!KY$>ztE!Tdih>fvp%}lPafKrGBwd zngFPQW>jwQhI^sK<|X`{(nZZr!m4Sv&NlxonsTY+=VbI8!UL~;h%4)S$K(?2IvaeotqUp@Mx%9-t(UV;J|k>DUb z>M^rQq*Aj$b# zq32hblU8!d?Fnr$If=-eVu{?c)fZm(iP(Ruc?Tu}&iLQs*N-in@uPIiUWtBSHtd+e z-%*`8`dRpVM@n{7yL_En)3bU-1MvVj`*K70@|QXiJIz%vZS2B#zzosfoW1Vm4_%DA z;|D;4kd#BP+*vVxc^$%kH#>27i-Fa~@nl?$@e_RgEy7D*$cFoQaixv{zFygz%6{h^1icY!9KXf7 z47_%QH|ax`-%^b2l6O(RBVSmda1LtaOScY)(0zg;kl`*xj5J%Xe=T-mG-6PUnd;F{Q2dZ}oMv&P&TMwd$juDyr{Qr0zL;cwL8`Huq7d zqC=a&=L-qma_xCLoiSCHe4kn`I-Zg!lL3^!WKQQrv?mxNbfyykSs25}W|qxqJcOV8 zVsmqn#%3za0W*02a*^DzkB>0pwuf&<;`)okacaOtrYhz>$ve`TNx&(Vv&R?gw8{6WhChjXvhGFpGW*NpL@N8ml)yP)i6+drHj-Y16n ztrY4c9Q4ZKX}R1oIizf3n*8tzbltV z;h{k4DZ=F?Y`X$!mQrb^o@Waa9YG86aV@bh4|!uUGHeg~OiO-lJMvh}u-w?wi#spY z;G`eVk_V52FzvANkcughidnw~vxfh?egG^}6|?dFG9<6Lre@UoD(_OoNrk+f?Lc~e zF8dpYmL2>lZ%%yaL>fm;*=O1&B8}zxH^FkCe8y}YHhZUIT;BJpL@`4)zNfwnc}y9y z_S1xIq01xFn?5tjSpk=50#p66%no|_xE>BWn~UHfzh;yq`{9Lf2K!d>RxaZMYDFix z!BM66i_{^5x8myA9|R1k97?!^A%peCX4QH`zTPXMlS(Vw!l4pnenIcMu(){qUr*BS z3DBxcvU8ONyz?_^*{B=PosD<=nb?4g_kF~b|8YiKx74}(536PjU4R|sGV)M^5_vN@ z507vv9fT#7p1!bxxd|pERcS~Yqr;+AL3~WXAHuejTG#wae5?Ycw+TM=k?EP&jY3Fm zaKba*(>0Q+Me)uo8nV~aDIH2<@W66ir<+$S&u{pgOJmw^Y=be~)2DRI6Kn*vy3_c4 z&Y%UV9^?^e44L+VGJae6Kmu}qC=?2l$;#%Ro>EjEHVH_TE#Bf+$a+ul8>{HT+E zT08M=Pe$MGfCs<{-VA|9`D|ax?mh=>E)@Zzij6es#PseqHRT!aC;Yr+`usOJ%W}*( zI@s23CWoBtV=#Z&q-;5t>SSF*ykJcSx3OSmSgXCw7qVqtBRRD8-R}ocAi-t2hNlIC zlWuiaO`j7C!>lKs1mIc2*cP~ ztwVX-X{}AI+XsAf-gSN%6{ym_FX02gfgg-M6jz;45sM3B=o;a4gV<*gA$ZvdmjxTs zCP=3~QM4%~&T2F9iP`D&JyFlZIc}j&-~J3eh*6Cw!?903NjTZ! z5l2B`rlEjn#eTlb`;__6SX{X=Ts6e$Ylhrcz}Z4hiqcH0) z%0|#k$CME5>>YAcX;)rddQ`_kobaaO6^`11a3`aNdxKL2gKM-~h+QZDbAehWPniftp+aG+W$sO|e~s@T}d zWRrg_J^ZQB-WwH?`=`jrMYpp#lG7HyLc35brmyxjSu+Bw`6!M~iZJFySUO3uXNe7s zEl+iH7L@{rxL7QB!bnHME=&DpQXEdc-3#6uxV&RX#woZ*46jLVM#E?VpQH{+GRwg& z+06q?LM%1;;^?M~nfe-}J*JYnh1H%5z9Z->ci4+3e|M)Ae?IwDYFUcwI-u1#tkDGm zB^xJ*P3&F?gKpUn^vX@>>SOOwI_~W=?S>(oL&SL`i>J+;ER(CqcEcYAtwnD-dqjl z96vd?{CFD|-H2-GOg-SdwlI!?o)~u?BU_X2Am7iIC@(D)02?hV|Hcd8A(3v|40-5r zI_L6dqZloqUaYsNjg={NDxVvNU<$? z)h{Y==tTHx4Aw=&z+Fy(Rf}Zt>fR~Gpmt$GDn;V3+gEghK6W}gf37zczZ3l1^@h1c zTB|0g7Jg9X9m+SPHT_qDe7YQGpk&sZQ1d(8Pk}__ba$WCHbxi63iqk*d z0L5J;5TSjdmcq}Kn9iL*f+GABn{1?3`2X!l|Fr_3e5JkLM8351WqaZI-brL$r8$u* z)=ZeY`b3GRLlt?~^^#abj*`MjF3VG7f;Z!Sp$VmF@57r&56$K3EH_NX8qTN4$}W}E z9d7Vh4ZAwbDCDpZXJW%)+SU;^%7r3n1(l}kvd2gdfCMbl*X~Aem_1+}WaCrV!rBb$ zNiIDX{e(=3Z1cC^FYuF!JI(h4HlG-8m>UVO^p>D& z*qP^T>Q!4Z(~L;tFD3Pn7CkRrbt%5g4wc7_HLnAJoL+@{PTWh?Y@K zsyAZM7DbCt9b zy{PAj*E=x?IND&rra*t)i;qpVF4M{v<(Ff|2jnq(47Z7=9I}?tQ6ZM2=n=KAhk49! zu};zP@6|M&G$MKb9tQs{82+bwlu-2qlG?MA#pPg!qt+%W=gh&xbZw^`cBBak;ndi5 zsO?xW>rN%R5VtIazPq*1ap#V|OALVRv?Z!apM4vIzCd5Jyc#Hxh=jtL(z|bdq!#Jm zPx(3}?-ut2wfgA-^aTFVZW%(DMNPLv2xffdbJ=zdB4bNHr(E(eg)HB98pxudUR5pZ zsC1wkw9$(6mvi}q#sW`oYGYi*-5yCkD=91$f5NxTBoK?%l4dV7|w_gy&+1 zKO~82ztI#T<~Vt+P0SF25FLy5~mzjQ>G`g$*t713_R?&74@ z=ZdOLkD3?E5IV9^8&Ie~pHYseN@;iP0#9ZX2Kttib!km35sZ;`d`@@(P};`X!0Abh z)$p-5rPf5S`E!%RR&bP~YHAp`=l%9AtKqfj&MuKn84bjB05U%N*mZu?byxYV#%Ag9 zc&88Ym^H?N8!f&rhIJii`fWS*`IQxN$_}|(#`x!JOpF9hG0uJrWp4(|mGy^uowJsW z8gNp4Fe{9~TV~ThpR74i;`D##mLGjJieBTaUftd_XTapKLJ!smT}`)Pf|IIxVr~B> z1+RN-#OPTGW@ypVQ9}F<4q6TRW?%B#UFr(zp?nI{T#)K3ZtD`P;HbE`41} zcjpx(wdjYhIrt!%n-tIIABW9B76->6X1E$RPRhlc^ubn(Nfm zw?)hc_*@q%hs~0iaFbxq{gMgzDNt4y>R*Zi>VPA@-ArwvHMtYJ<%oH)pvco3tfVeC zLie&;X3R!bTJ4h>Zl8Ya1Zu+N6g~neiJSkY#TapCQYJ@_buM^~zWPXMBoc3&z2~A% zbWhc=pW!z>e*x5yoPK%R#y55qHQeyYlO}I0_BScj8$^cGd6~;Ui}5LcIb4T*($d=U z6q{0*$goI<$9;!_Ri`X-q_ak%Z)3hh2Bjtvh$eSx`{PC`h%w^uH0R?)P@v!5u{=eJ z!OVF8*x7)*2sEd?BobOr7Je+*&o*Zky{hM8i`Xw>r~GdHgLI#qdWr?)%$HbE8@rSE zIYEgM^sc1Y82gqaZOkw&uDUiZ!wm64df{AkgdMy?MH1uE@Hv+UBHqc>54yY}kZ<1l zZQx=q{z9WoLXGV_ew2QNd~Az#!A5F_LZ7$)?NM8{haOy)uz_zC+bReNZIP@rnWA^U zzeYeGy)~eIY?pJXoO8VJxKY0;u9}$eE~xSBn~dsK+E(ri6GpL}_P?_u=2BsFZK^OF}JQ~|rA5apfO6q9sBtjof z$fje86f*KktFCtJ>q3bEi;N*vbIiV)7@eAGK(N4kZF_=?Ew{mshC8fwmAIs z_}L*#!azcn6+18`37!LW%DFdY?+~voNTW3|6=5yQw=*||B}X7YPK(A(g#E%Ip{zTf zx3*6|$z*r@_my5YGq^e@UpyaAh1P*aU}gfn$5}9JG%HomP5H(Z2qA*>giIgn1qf=M z)Fe-~pe(++1!FTvMQlt4x3TB5w_M?LGNH=1qgDIG3f7wjw8b1&8%1J@k_Lg zyE$E%)Ttc5V`ErEUvGdYP>3Zl3hbNn6H?5`blCLT+n23hGd{G^Qb4|62d%$1jc^N+ zQ~0I{=8Tjr1%v6O?2E+fd=7yxLmR@Z;ugi_A=C=}p)of$P=Oin{8ktG)C zI!KOzsS{HXS8Z@=-<|eLP9oALDerJ(5E3LE|0yIamTo(7BnImHl;t$>XBSFah#fAH zO|$X1c?6`p!{I`#*d9>QR%nH(IqC>;o<$102nG z0yRMMWX`M-V_-pooc~AQBl4zb2p)c*dsz|pxJ>mb>FB9sj3T@rC%C&&A`kgZ_4(BM zD51G}L600$eb4WisJNZk=bZ5neQJem$Se>@&_@#oNhIRZ5a;j$Vg& z7H^u;Y{Ib<@Twx2`1;v(>M+oBBrzW#eBIwCE_2C@DyIW=HrISUsZl;?)6m)^$fuC! zSFQicU@gZiir~Uyq-0SAnJoNW5;INaha7P;aWGyIIk|!TjgvPTaX7E*(fDGzWBF2C zu+iCalX}t{`Jm#x#hikm<>n__4x7AVZ>2Z7?^tI@4Isbts_3B}=4$VR4Cigr8S+=q zz}oYfCtD8nG%-0J6y6uuH+A#TqByW?5|8nq7DKq}L~2K_@Ct^XO-VfMDl-Jj@YuE`mJKzeL$PNDh7jSKE8 zu12N?G8+Z6p)lOtcNsw*kr4{vL?qk@^K|Lex>tne{!12|mGCYWl5)O!33-*e&SZS6JxX*NT;EhxDb; zS%wlI2*_M5Df2Exf~+T2G~ewal68#-3`cf2%``ac;=Z5cCoxX?Ae_Lp+B?Shy^B(* zn0*AHalZq_QpyXeRe=j6+15Ujh8_Y>2u}hw$rp62k?X>njt==6T(si3V#>;3)6cg! zCKU&%BR}by)y@XXAV|iQ#SBJBQk**+Q=HiO(^$e9lPl)q#+9TO1A8F=N0dU;%wTSr zeMzCY)_m>xv-5*mQ@!!x5%d!Vb%TFwAKXnR`j(tjgj`}iG01rF^ zXA8z&F=Jq`j&H5C2j6~@N%iyW`4xL7FkN_vf$#gNQ4-vF`5v$}b$`RWyY$Srm|Eo1 zi@~3H2UAxl(PCehU!|MfZHBlV1m6=~IKC2_!KeQE0s2DS^B@3epz!uYbF41ttCa9R zP~r+|ipG5n5+XFqXzk`R*_HO_O^$wXE#}&uZ9hZWS}4eF7?h0KiR~4kNsQYF_24m8 z_$hAsk1zY5m$>K2(%=gHSPRTSsG=jCE=nM77yQwtiynB_9Q zIH#tP6UNdU_$|cpo>UKBXkMOv{xILe_yDkJ{IE$wm%HTtJGh(dNA}>WlSQHXfim3V zjt9WAO`Kn<+nP6yRKK=n(Y@DSzH&wTU4G7dFOc~A+ZPnkGyQizoxda5f)ZMj(vC;q zRS9^3wO=@tm(_4HB5EcidBHubTinAm2lj~vN-zo4|4!A`d!1*7{yn1}XoZguUz*eWaSI`qS$>sn1f-58L|jA8FnTUR|pA2J=y5 zL|b|TX>(pZ0E#8QKL7?a@5Cx4 zzt(89Byno6+?A4ij<&FE5UwljOV)(j?NyhIe>`5juA9RlKk5{yx*$grIJq@7y_krY z4)b^OkYY?1G5u_@@BqMj%4vG$sE_~ZYc`j>@$ip3&wwsekt`2f{IA$~^I1%YFo7{1 zOEQjhidh8%KkuVvMf%i{bm@b{F}QoQm5gOI?(jx1sgaT(eQ|c7KTlW;3q?%P#d3OGxoEtRr z>C1uMq+wjQbmH|a`-rY~yI}hphcXXtWW>AgwCNrx}zy?f9eXKF7SS1xk8aL+B#2~v_AK9I6?iu9`GJbc>W+R z_Fw>!cO7xEl9ISj{5Fff4Jnp25$UM;Gi^-MkN%V|m*p>pC_7FP3z{M4)^{c~ow2o! zRM1p-!yV3;cC29wPM#~4dWotZ>Xua=x51U9ju)Rh%aOws+XoSqDOK_+e-aCronE`} zt%)C}qo^Z$ucH|+YAzdZ%;!)Ldhz_V(&NcnAg!h!(b9Y4gdyu69&YWdyYBNB2JuWD z!#E4XeKcDw<&-kcRaPUXi+O#d>%kFZ0>sV6ZNWnt2 zP;DlE#aOdsUXHm|%bP{aR5}D)W$J!`m1je_t$m1J1D!`*%m~Y-c7`MI8oWMrw30q( z&v-u(5wyK#cbYTvbkkz%b`S?&$+E_4%AeF9N&7N~8s$vlYutKX2V!=?J**N^~Z)h{MFBD;p{4b0x*7U%f7BUNFr2j>(G3mTeBF**saqQ2Hi50nIS z>!9g5K^yAxmyUF5s#f+IyYmqN4mQtm--cC&z#M5wvU=Sxuu^)7Nz-_n$36Htw>^wG z6r`aAQ4I9HWWk`IUj38cD804@n*jtMAP%)HiRE;yT`&5`_B+`F;5Wmf#pQv{1K`(7 zq?+cef@g2mh5rhCUN_nN%dXiI1nmQ0Mgia#y7h_};rekyxvgLR0dQ>e>S*w^mLXm@ z?ip^p84r}SuEvOmgSh8HZqU|^kc3rGfZGlI*Is3iG#F^>DKF4}H4wb>$ zOGX2ZI0GytiH2n`C#z8i?p-kh5D271X7;nVPO@FeMMog+`S=+4nE3W`PjhGR13qBA zNG#fs5;^+G^h5CR?bMT z_{pbSR4c&n=)PXl(FAG}x8>H6{?VPUJ~EUWxBG2$6CN>zNb{$c*y__wH)NSv8VgYx zA5ngvUkv=zj=8u4Nuq}w|3wZ%!Xn~>!nAua_4NzFUsMvqAzwc|v5M&ovLt0iq7oW3 z2oWH_=~~r+b}XartAM~ZUKqwr%)DxjtDfydG=kJ~t8|KqwL4bdVg?m1#c<^m z;R+!;MfLp_rM8bpNgIHC zoZMUnt5mgAiueRXoQ)C!0Y@O-c(CT?=5E>sBaN{jk3%VyJ$S2L92Tv z?)&lT1+(dAS*;bmKTjahB)*pOye#@~kK%=v&c5U|*oozopoZHh!gO%|>V#fXCx5F`<)lu^zTCGG3p8GBB1$aL#d9j+dvM#8 zk4>$64aR0+v}J-xNT7vzu|Bd6xLj@#w0x>Oh$*M>154;d-h0irjjH26QqHRbYvR~; z?S>q?BZadnmh0w2Une4Mr(Tu_oq&=yuq|#WpGO<+9zB}mx_tnXMf#g{J3RFb;~Aw~ z(mgjfEj*_hD}^Z)r%2hCoQ`1i7lG(#`EJx|N{D2ahzuK7ohwmW8*8_xO1UZ422zMH zV;qnfHBPxEQRMV1_Vt^supHvUy2?Yvn7pQCWK%SF#km1-4-LoekS{@84bhgIDnVL0 z`8IiKPA>8R^-p|b7Z*XuE!p!vegATt9QE?BJi9E^=G2%nUwI3d*&x=crhazy@q7;7dSB;Z#IM^@qbIqSc_7271 zbCGX{6pqB%fW25>KI)XlBE5Ru4#U0t(EEi(y?e31%|U#qv&u!-N^cc_x`A=t1PDsI%-BzXs4_s2T9b8_{=aJ zIZZwI#3eFAePrWHcVna9;`UccZIcOw2KuHJQ|3eiT1+*}?yBbK7b~9&k?jA$37$tl zbV<~QK=C3p@7DrOsyXBFNcBELRjtoGMxUa#UYS$tHVm@t+tArqGPV~RnU(85ABn{A zrX^x)h$vfhV*;7+^_>tP?9eXyp+|-539w2%Q}(wf#wNtCovX1cjXmHHYS*T3Mv55; zB@As|=$M^za_Ye5h+L_@XY}*7X}tXgCqY~T8c?b)#^2(D;1l5pO- z)h*ZCXyuDX5#YwO0WGl=gSWATW+>_^l}9W{I2|Y|?aB<0QTSMdbC;nE!KG60|7ok1 zj1pz)g8X?8c$b-IK4&%DWnqys`>36fy>5}qu3Qh#QQoLh2a6A2jVC!lOPo=MI@s`~ zD%jcJdilt#j_tRAw}W9Zf2F)BehZHAO4l+HpsoqT)ZZ@g-Lf=H4AV?~?vJ?v40=of z@GBaNJ&ldM7#t3n*PSV3iT2IhCL;}eQ@xE??i9qJMMbUg>vHl$PqZ_V$t3E1jT9Oa zGn0}Oy7d51WG!B>w67044a#N;nt6-Jl;f&33$MZfHJ>e2)fqMPtA@mZZLobY6^*~$ zmnACKts9EIcO*HTuUpHGB|g=S!c!F>+O%XhuBq_o3nhReGZ3{XgyDtLM!$}?9?>(U zzauSQDq8A1ZJMWJXH5X-Dw3DG6G4DXZI|6&MTsZU>!F*X*Ctu{DhqL0L?+uxNUhI@ z2r7@k;9e}&bTlG5_m$m3qyznL&sY{^^=ND(;gi9~lLcZ<_4la~f$TbW%LZU~<88_;*e$4BW%I ze0#cFDTE`>W+chlMW!)q{;~zCZmRbOPj#DTI6ls8$`{Q$Q)%#DgvvXpI{#C16(zvn)U(wumGJXuPNc| z=T2!*tGV8ZAl_Tz3ER9&p75P#pzs)$BCZWA%oUyvTuF^3XEjcenVN&lbeY(+9(zuQ zb+jnAVv@HC03Tq?Nz4orHC8Td>DM5Q>E5Eq|0%0LV3r>GSt&{w=UgfJ*W1VB*~uhl z#yk)I3fVyfDKp)wh{)Ii+Ot!a9}I$enx)Rd7jg@Ai)Q-OzUBkwj07WP{sV-av9lEB z)pNOFpW=RTKF|16^K)3A!P{sDH<+Esl5=&)Wa1-*>Le3;SKfLf_hSrHi*JjWO`0<7 za3x&1{XxyNd1LEN{)rDe5I%1fCV;czj3oZ?Qzu(@YD1ZH{vdnOV_<7ofxZ@UMEPDh z5w}Mm%OSMt36aWoR;zCNF2|AGKZ@(;db~%%CE)OZ_P2N zB+fsSN$dy5#B4tKp$^+LKrCn;VA# zK7QV225lz5Nx1-ZeLi5l(7T|@+8q%phOF~`ou)@q{3Wh^j7xm-i7387C?dv&g#y!) zQITD*f~*sl7s`S7rWP^$TVw1eB^3Osgs)=t#W3JT|35LSsNfR>&fcRm#wq z6FU|A5^G@CkGY~&0Cmr$uD)NJODJ`?*VZdLHNvh0PX-h_Az5dSe&eu#VvaNT&G4t? z@MAy5y0uI6*y8J|(EEHQG;D>SMS#&iYG{TV6q=$!XDJy3fYbaum=a0o8?r)QY%maaf{<^*JXm5*a~1{#^vZL6 zeTk!qIzC(O1v%|w5+kru+}$yW69(0W=OB{dYKqN`NwkB`M$2hXe9~`ZW0Xcx93&l< zyBUvX@-dthawS3OlsQ>!KM2)g8jZ0)prWrXLab?p@49bX`S@y=Li?)H+GwzH{tBZi zht>W}ghA2_{GvvCQ2)Dk11atHe&_*kMD}~$QBdTnLsEB&7w34&MhP!p;Gi$#JPL3bL9h41Cxg zN!OZZXpvJ)LxnAe#sCvP021UBA}Jr!DLzV05bY&BI?3egLp)S*-Y>xBPd2rSYYBea z&;gge3>c}sb%0ifREa{%k{Jpk(;Uh{E+S(xoq~95UYzT&H9J@46Uc+aqqYnYp06{k zkSX>0`M~FGrxbZe3S+!HM>rQYu#Hj$DiobD&c?R6&DMWj(DU(3Ua4{GlgjES+#|8l_Who#!RTg6D=5mO zPDV~ut|t3A7iRBr%1R>nPrHS53IgBg_aaccOBhHx(e+u{{b~onA=@?w#aUBeU8dh# z$OpheE+%>I&a8r*>DCVzFtq7jc<4R&htf#i*o9KuLaj+hPDBhKJ?pq8)asm|$`aiY z=FC`CDIy5Wr3g!Gr-)6whxrxm_?>JD&GXN{6~EpjyYk$YJ9_?^f3a>)K{_p-2az(Jo`8wx&)_4wkhNv)p}ZEd0}Mt>JqdIkk6?21FrGzKr}cJ3i~$JC)1D{mJuOU+%H0%5QLn@p;X&>0leibmjcu|W ziJ($Ic>^gFFqu@d2;5z3Gc^6gNi*VE&RWCp1TL%WD1)k)q_)p6#X4_uRJ@{kSQEX^ z^lkZIaUwONZiokyjDDw#`5UKzTSd&oVO`?+8Y-@F29_Li&A;Nocc65c3 zu;&2)aoF$yrz=v}3*QgtGf{I;-ZA^yCw7z%K!6Zx6KmOt7ZvQk>HS_NSANRPegmx! zz-C-!cRs{P8aA8Iuah+**P3W$c?A~ySt=|ez8#aLC*F4Ct~xyb{%NI{lJ^o28Q!~&(F3kQF6Yh+{GDp#)I4< zIEy2R7*(NrxJBy_zKTDz>qKsrMfR5c**=f|LTbRic%B{9@{@d1HsMeM*gid(=Vo~X znz#+MepK8wmsq*0|C9Y$f(wgkn%Va5=u7)QRFL$=bkp$Xs?1Z>Yg?Mtx_gi8p0(4J z$6wFd*R~&jG{fHMe~QlNVTs9oI(d_u6T23ClbcBYgy~0f(Ic$vUCSRrj(F96W&iE` zM|0x_d*Jv3;9o}QpXdvpwc6N-sBVfkwmZ4*e){$&Cpj)PWJ%rZXw-vh3 zrd#Am!LD5yK4xKe$tW{W?r0R8pM$8Eg7WBnVR*XSR9tOE+62>g_!&x~#a@yJXOF%~ zaZ@5jOwC}fKRdm;8GQD7z%4_LIG`y*S`IfsZiJ!5?=ck>Jl7T(HeRdJk|t1TP_NVB z+8bAC?iEcuMuwe1hcc+!eM-|@%xH%X7K%+;-h8~T!d~`08$5i%_5{nFX4`3b9DYjX zW+Rkx_*So?ds#iwSk&4q!{0vRiRk=qoxU-O5ugtihU8T#sRp5s?^`FZi(&UnqZbW) zs#Ww#A+DaXVkk)L_>Ip3R*2wefl>-?uXKbsQz=QcKF5s-nS4v3b_{k*>jS_xe&KoR zgj=bP>{BHtS*@{D`-EKoAyG`BqE}zedjqDk-IH-M4?hTOvDWM4+cXhuRb&q4^$!ps z=+o&5Im1w)3^G|F?hSr5G4uMiCTBk2rrITT@0@dO!qDrGMOR~92DdN@Zt!`rcuBFh zvsS~tpVgdtlfGr>gb%o|y5!(x|2Aho&{_#f8hY>{VTO+$MARsU72A4s#&Qw!!Ld@* z5G#>%)1vC!I%%Y9D1b#BhQ8_MIX|KP0arM5m6QjvCWeT93O+U|H*w6EtBI>jhgs{V zwQw2DraKA~gT$Rga#`2>b2q(M8Kn3z@=d?Z(Yi9Ej}}=#h|(wYCrC8>NK{-j0V`+h zJZZ9Z4E*!Litr6-n z6$?&c`|EbIj0o7IPTTf3SI<=1)R_N77xTNtSh zG`Do;mh(C?j9cBk{pU!z8jLOXzBhxkT3WK>`_E z;_dm_tu}SPMXo!Fk4}4y2vW&WmEl)n5!n1S{1ijk`h3Rs-P7;SX1ub*y~GC2G}HYr z{E0qf2_?f;k~WyqnefGiIxjSSy?fb}%X2QN|RdF@I|hyLeuQ(SC*mqi_*cCTBG*{A8@ijOFLGPesa?x!t56s!u$Sk zSr8N7O0^)^oHw8=pK{CijaZbOP@I9|C=5aI7)tfRpmH68g*Qc`vAHLID8#lj|GBby zFiuQc#9FI-l2RWR=d3MwR_-RomZ`@n<&5u{a-p)d-dqq?;n{T0lNK7L>=EhBll#y2 z(tikb(7pP*y>aOGnIzfR(C@@wg-q!qvPI{vKQ@(fUX3YcG59B64Jtg!8BJs}@2|q( zzba&=a7oil(}R%v!r^!5zIhV(2P)$rFOjc;?Q8Mv?ai4~Hwu?OKZbn_gkc*50;EYbYdcB4I#wPRzGkboGsiOe z%isL3LZQLSYsd|4M*Jp)(Ghq`;n)!m=w}qB9;tFx7@sEmt7$Uk{eZ;<1UNV{o-^Z5;Lom0N z^u$5!`oxi?^bDOULQK7ej$ArkdC0JN^V;6LV44b0QYQJ}0bsB;axp@z^p$pkh!SPu z0q|vCzAkI+=p-{z9jtq1=8v|(Tv)bR(5VEEJWp>KJnx~~61UN61g;9ecl3@BuiQLN z+|-jgCYS3!u`k23d0g=LmujM|94Sw@inY4tebcPo7QI6zLW7_UrW~$qTUkWkwJz0# z9J9CPnzxesRu^e6@!D5EA=4fwNl0cnhQ~Inp*NDLmQ`S5MA0*_3z?CEhC#eC$1p2H zm&oyjVP}5#oeiC+Mg9C*=cq++b*yR;P|AtngVW)Pqd`ZLjU`h~LNT7o9Wonfhn=Y) zSNU8d5Y3cBrC>%jiB3fh-{%$M5re_!X{vHvG9xD=*D%uI(HVihS(91PFQS9$LFOu1 zX1XRj_YL_(ylf~TX>e(|;VH~u5h}qa<)RjnCYK)1X<*;xW$94jN?)`_nl?o0t2Hw? zQ#+thZ7woG89eF<(Ri5{D!~iPv4=)KopJSFd`YaT@-ClhWYsH3=;=G}Y~~Q8PRF_P ziHV6>1u+$s{5j-2C8Uw`EG}k}%JxH#jqC(`Xa1g@sem1dt(@ zXZ#=X-ZCn#E?XOg5Fkhrg1bxb;0_5IpnxESyF=mbBv^2Fm!L)AZV6Dh1*yUb?hu@y zx%KurZ+G9@efstt_l)m5V|;tmk2R|Ho_powr`tsG%MpHng}-w3?r3iu zRkMbWlq@bi=rX=*fu*hnSY%}v!rTDU!5zwouvtzw$>5AIPc-Lzqm88v;&iRM;#Tkw zhrGt(zN*jA+*(Ao)xK`|pdaOwh}u848Z5LP9xh+3nLUp7o#%<9I=Lzy9-X*kc+S<) ziikVr=?CvDA0k29WX`I3tSajQLyb3=IVMHEOb!&vb9z|@d%cMs&x*xMTb4+xth6>WE--{$Bk?=&PC=cle<85PpKwDZ*zZZMk3YF1YMe zz%{*fKy`Hf8(-RMZ3o%Rf~NLPGQ}3RS=^Zuj`I&#Zl{i~lZfx_H53_t3Gx-~Dc2+f zYekR%b-4n)?Ka9ANcxBT&EaU+p`@1!YCN=zQ}RwfJEVI6o6ZBN?g@xT-Ktei|xs!0QdS-Yxo z%u0=;&@Q43IT}OxelR6iRy_v-VP6 zvbW=Y$m9c&#B9w|MQx^+~mb--hsg4K)tOoM@8roUYuYO9vYEiJ=^@~UHhOZsfF7MpE#u8&zq zw-WTjv{1ACGJ4TApJbu31vi700A82$>^uz3(yZ<;w~Gii%s4sG-q?tsS@RhQH(qm* zYEZ#vSy zneu-=AfT*E&x4{{0`Axx=lYkQM1lehU^0Lr}W=RBVxxEs1w#Kto|{5QOn|N1@y z_O#jdCm75TVC#;(h@34{N~qi_OaWVWd|W5(PZSZ1`yaPf&iSG!Y28%U=`OV!EkVq>DX11 zQ)7R=cfW9?%9f+vw?Zbk>ZHYbxm{0laWhJ-EtBGa$G}CV;_&h2HxhaEb5O5cnv8pj z8YI?{0aMmx%VM(K?nXV9s8jhe7cgZpOmN+~*^j_1>}5Z1FS+F@Xe*3qn*6rKmF|;4 zsVo$lIP~5Ap@PwV320)PrCcvL&2ofVFZt{$Xzg{VJY{ug&xdc*EP|`X)Q9s;e*JiW zJo_*3v{&AAksjeO?Q|zUxjCOKBJ82e#l4kw2SvvH2Sb*A2`;rO?k`_tE-FUZwJL;) zH2V915Wn}zy-zK;`yCexxNa%z+^!AZ)PBz`(KW56=HThzZNIb~t7t5aU~>zfbI}~( zsG(;ZP@xM^Q~SW|KRs9VWkBD1>)bh|#Ot;Zj*d5mRo3kN*-lU+rP6M?rodc}yw zKski7*ucESzL$r~U>mVp#L^QDjk!f(o2SBCmahQWEtar`?%gd)T zq)qD3U4mcS%{auD>P}TIR>f0nWO&PO%B!rie^K3eyZik8DJ84RY6`4V=%tTrhHXo+im5i#Q-y}W=h=E75)S+{;JHP8I7a>pEOoIR+q6GcMo-WO|W4)tTMD1 z7;dWCKa=&$E9(s)Pqg!sVs_l*br+M>s>)lgA99nwavVQBNB##d+|xxud3stJR4BLJ z5Wv0x>ibGKrh7=iQcZ?Djbu0o$39|>J_?%6K+V3!AbW_rMNT+Pb~37xXzI?8c{*D@ zy2LPg%AlC);7ae;YJ~)SX2)3p45_(Z)Vdm0sq;P=r1(!NOL?XfWcm4 zH2PvS)ds>Pk^V)*Hv3>^of}o;Bq%B*94oX#pOL2r#6g@(h%5``S>T!HDlu+I>f_0M zobY++D82*eJLM?)TE7y!IMF)&=Kp)U6weRglB+Zov6RoIdqt^}m>wP;*DIE3$rRx( z6t%;Hf>chM_=?zJQ=01MQ(4;g5xzH*6h)K<2(!t3-}9%Y7-`^L;ND~DD#E^)EssTX z9QtHi)?9%mYmnT&gHfN;zxl@{{U72h-+UsQrIbc< z4r5)*oKn*Qxx?#Y8ODNB&8KvHzEt{Tq_8yAt+L2>X<349B5106)VQa(*V|kRg&Uvu zyi|8rb`C!#nGHSQXB9KL(sQ!IVS!Uf zr0NS9eeTb3ks%WoV{#-k21DcoO}kf`5wWCw%}+gS@U&w_&&8 zo3;O~!-YWxq$A$-u4@osmyqsM2G~D>Hr;Z>?S%#GBVYPq-_>vbHQfnSa{3)R%u|$-V(Jz zGwgkJ=Kh%!*{HPicp;zlXVI7F{Y5<&j;=qMbZEI?E<}W-I`t+Rs@|hm{<2Xs<-d{o zC^bxKUaa%is&%g(uEm%-Rdy&fBTsb-<$)b%#H8Th{ta$Ch-x zNqybb48aAq0bbEnl7ug%!A@}s-D@mv4}>2|9BdZ@9_1@*B$Y&Th(-=yy$^>T2aF!e zh#;Irm7`fe;Htj0we|BS3-=1yv9c^W-(s2IDH-A1z<`_mrW4d;bL2wjS8`!0P8q(N zO(ax(QJ)5WK3Kh^!nJfh)jyT`ra+!WBG_XPQnsHzT%%A>)@s+<6zi*oaUTTLtL;TT z?k4hjvtLtpx=;AQ-(gJ`XFofXs8Vuyt{x`@?c89-0v+nYjU9nZ5;G!)T4nS3+x+Lx zk%G-@D}qfw0x0f05XlRT&s(DfbFE=swMdFeu^F_yA9Rm!Xbp6Sk2kk3)8O$ytEw?s z^<_{_fvs^!uu3f7r@o(S4Ni zLLHmVQyuDwPw6hn-APP0g*9pqMk$8LIfntB(jN?p+ZjY#)lHh%)OBc7xoXG?lIBRu zbj?t7&F(eVf|WT7TVsfhKW}nkTA%P_=$WZMZ_d7$V7uR{1Q>TD2^(8Fq76)+&(viN z%&f%$>pR89h?}I!OTsm)I#cKGTchl;UI_h2`NUpAXB|UQF=_^#ANez8)41vk^8X>} z@Mk74gk1U`!Qyy!noKzH*)N}sDxdOno#P!M_B>1s92NPY`zfbM3~pPWJF*lqGmcMQgCBaP z38rdeLanPXvrue8 zY^+O~T94SCUyd*FX1uBgF(nsc`*1m;_fBZ{Moula|)>rPTv)$l@6J!>A%6d>N;N zZUR|@z*VVJ?TQmwE6uR18S*$xkyaIK=vuwid$YGhc$@=htmYmM@CUL(G7H=K2R!u4 zC_BTl?BuJSNJYvDb*wC}8LtR~Fb~m~z3zk*b--`#_}R>lgL=kFb({sqK2{G!AM1Iy z%Z74ZpyPE-H#mxTNRKa0)RG;ESifY7ZjN+xt90_(Pw`F4SQ%6F4<8Qgd$+Nky^YT# zBfpIaaUSq`6}*b^ngSzY!Bw2OcC~EJ52z**{(iz2i_eVIy&|QuI4b9eK8vsJsbXY& zAtUX3=w=XgFdk<8%4;Q`9SS~Rv(QX0oxu=hBN!%rPC}M`Ad|Myu`uxpf0p`*sh6_y zvl419f!v%{1Z@({=uEOC$44`W3)&aMFYcf^VjyPU=fWO+>F4Wn3<;v0J+y95V@uVj zrZZ-A>s?Z`*10k29Vy$Ne>H5cXhblxmgCvl2Sr-=wQK}0$TizzPN4TQAvIl>cnzhI z44`RFcy_oS%$GZieBma^j>t${);B4kR!0F4s?z<6R|Txvr~47gy#a^!c$LEl&0hMp zN>DE(SAObDNi%`H`>DaWTcG)^WgSyv_fsM@o<>i#`jn3=+9)c~jDzeru!&pXjIG%* zDvQadTO7E<*Y^!_6(?dHNB`{b4RatO2tNj~xR=IGRU+{5CHLCDv90J|W|U94caPPb zh@rS-gDk%c&pun>zn3BR7);Nh$~;BVi^Vs_b@59WdiO;LOD)T=F$%a$qRrtCwxfNj zd{QCBp2RQxI+%p>gs82sZxLg{OjQ**uA;B0Qkve;6FgiO%iU;_N`qL}NQggaPhbGD zkB}ppcGZWMvrk2lQoB<6@#OJoA?5YBiLC-IQG=ds1zuUYd7h+dcxaYL{dGbiZxHZ3 zpS=T;DDBAViD^xV*W;dE6-k6opoZV8^nyugm8)o7MAhl~8m^#FOu7B294SR2?7e6; z?i<(kckgr;dm4e$tO{%?X}mv-#+3#2hKV_a&R&Vzjea97xa156>5KZH^3(Jhr)l%R z5*PBr(cgKKj7Njk%&-_k!L=DxEHg@K%qio|wnDS*FNMC-=jXz+dIB8J*j{^6l66kpu> zTlAdIsn-I~8iL2C62iw zp@MNTm%R@1rz>b&+ECNSxcw%7$J7Ttql{hjiuubfy$e`?RX!!y3V!1_4ZO+y=mW{q z+xn`cwe5raf6}SKJTH{=@V)ICg5Rb(zX0P=o6)Lvs7J)C`^PRL+ZI33XsBW&EDj1{ zw6sJvd&0#dGCGRwZS_a;^&c44&?^v(ScLzT{J}RGEKry*ibI9|3pRuqz|h zVw(ZV0gyg#waN`|JB8*h~qAfHUo=uTA=A5m<;ApsOx4y(G3WW zLO$#fdeKY_k%tGUc*j>Vb@sHB@BVk7fyq>5kwn5Tw&sAjMtIAPx@n&?qrOzfAuaG0nh^uZe(fzIXK?m(fcS`uftCS}hQ3llJIt|6 zkw7zL_@YzhAVhaitzIcD)w93Ym)ARC#GQ!M<-CPdBsw07Z%x1RGHcwj#Fg)0gJ2qW zaZSh9us*jf5>T)0fK;8P*0E4)6k)w)GF-y=UfJs@p?E0#rG%nr7NCL<)D}}-?32?M z1evKF0>6;~ZLE0^fsB19Da96nOL7Li z>i>9fV5bH5`i+zdf3W=Mvq*56E3wu16~PtT)aP#%cVq7eNANGy8tH62>gio(BLB}h zTgXqtV_b#m6-7OdN3#_I;gQb-g+c*{!x4|7mP=ed;>y$wUHC7{2y@*NhNSLpY<$=1 z2|MYlYqYEWNBQrBC&+jmNYH_Llvffr4QREXk}M(5ZD%3yFRM+F^sVn9Q?2R@_|nOr z6*y;WmpS^pL-Bxg{RPCYnUNB3F(+Cy!pY=YMHANEy0}L7#<}ToDUVdFJBSgYHT%@q zU>!w%U+1bu=Xxdb^4U91u1W2~RwM7Z8gS}Qv$_axj$-Ka*Qml#f6Jgc?xa#dceWKu?vEprqn6{+hbEV!O*KRFYAE zlKB~H#V^;`s!QJEra20Hv$Lq?IH=%R%AR_*JH?R_aebU=?K!6TEJH7~apBXJ@E|Nd zD*0H_6C+D2)?|A>g%e(&(*de&`WNk}XBa@+y`MxIA@5R^&F82^pvvboPjz+^2+X+} z&s%+RL=vMznxDmP(*awBKy3X2bjzJ43e{rJZU6}q`KatjuKrU(1HNg3HXAJ&LK2gC zR)Qma@t+DQ0scM&T$4+?W(V+cdHPIKwI@97&mt`?-7wzO4)^$)Ke6<+G&gHT;A=w3 z8}H_PRUc!JwyIZp7ve6g(b)s8C}#qT-U@-PYp#kONRda3IzKm3?S6lu`*0{$_8Up| zqs#11lOwrXUX;|YzAtShCiDM$q|j_?eAQaHPY*oy_&P`BC9(OP;`A@hG^3CI%$atu ze9)?YOpol@Hg{}p@*KHwNc>o~m9O^+SpP_{p&x)zJ9j4<*&dU*qWD3(NRH)pjRAlz!M30rx_yO5gCs;D2#KoDAN?}cg9+fRmn%tNWd z7|M>w+xXWXslf*--*u0Grxz%Hx`zH4$(p|P%iKEc-$+CpraJ`hyfcYZ;Uv+Cbt?OQ za$IwILW)M746T^SrX+~m>!?~s624zKaax|+HN(;^6^$Lhu+)m;1ORU25;a`?u%tO7TR8lB``3d{Q)wLaY=n)1^Bh50%2a>k6yHM+vOdqh|RCIt>h>0 z_LtpR-ykpPic{NM)Es|>v%G$P(df!9!du|2QeS0M~jgPeD|6F@*GjijY) zf|XT!8ef+pxBc+y9K{m16xG>*&bE6C&PfZ9<%w~x+T^$w1f^s|@Dbo~9rXPiW`BLi zf?BwFqyi!}K6b1#$9R+e#9iyl#c!lR7l{rxl;Da^rqV~LgollMath#{n8X=*+)oOm z)%|Muz28Xjn}J@t=;%Nn6_xwXbaw-91%<&=K7pG;R4c;B-7v$X>EB2+gO8`*U%aTe z$j!TXA$J}7(dmD%{P$%-ziRNjf4PEd&(VtYDry;eZ^TjD2RLZ4Mq0*CfAKyY{dauM zh+QdyPi2+~F@9d&w@loP`JkBnjpmTlK?_P5*L^8YR#ewIJaZc*Uhct3-t9Ir5DhOg z>z2{2o#!L|qHV%X{hDH%Z+Yt1(Q(s+dSG$M%C!6)B#f#`Um;yvAqiaGwXo-4Q$N$D zm!r~X7I&;3twQ{5sc}j~>YvPke|y{`IANZ<1fDwmMjDC!jnpxl&oA~TM<0Ht%nWgE za`%>Sl#sQJsWSnI%TZ7SZD_SM0fSj<*H~CQM1eR71VA7$3;E_2j@+BuBkes7_?wCg zIFEMI2!GLMd6A36__J5VG?R|=mKA@Uj`G9ZK9L)Kelk(!XX>E{;US!#Hqe7+`3s{{ ztT)SRmeN1w?IGqmRM+lYq2HuvN2W9p_J%b)&*KMs^SptdA&lKI1Y*FK_HvuujaaY+DOvv zUaipTm0Pvf%uT%HzoxI?FzsmN-?NoMzMqOdX1e3p9}(j%K~wuEr&g`JVvq`PwXv19 zsz%GaZ%YnXRyb?6jFYqIcyvhBO*uWjSTRrt^jL128X?uoJkHqj*`=8L2U@HrlybXw zKk({LnDYNcPS2B#x3&~k)7QdjR51k#Gn2`eXn1tC>p!=+t`elEoa~k(+51}27Ck=T zCZOZb&L_$;^rG-X!0@5f?5>k+PH@B{$qw?DJ=={8INqMmRXVm z@@`Dg^?xV_`oO0>-D4I;Jl z5gH+ElPGrC6!f)_BOP9rfu)yn2~igE#(0&xmioviy)K2)^K%C&kxo&GxNe8}N**CO*Q$-_KrJRM*W4=R|> zz_yp3DxdLyC&&JfT^CGu6$OB>T955*Muz}bqvIWv#2o0g1BBiahQEAg25cw-Cv^&c zBbDVyD5V^r>~B;|?^lu_;Nyzh41DnC+ussYUA@AJ3)dGzq717Ce)4MObct*-+*^V( z6zq%2yFuz;eNLkZfZ!MJ#pNn+r|05py(2!HC3Xd8ACJoo#Q;6C?s~J`fWq9aZ**dx z_w6$2GWT^v4GuMFgaDL=8E19Gb`YD=KHUc*Xax2ZzMWJ{GT>66UCcgg?Wl2Wo*o8{ zany=&lpAl-F1>J?ma_u2O|4k7`;!3eOL7Szsf3U;R^aL^%y^prH`3q!vbIv={~v#v zm{!IkW9yXz-5;iC$HXb$M7=W=&-a&qlmA~03Hq6WM46BF*;`rVuLM~}i(}uDC)+Ww zRqHs!VjRUp()#BhbN*XkN#{Q%Cf^VuI?#)N1M)nc8-A=Cu{hNxckk!|(Wwwb6_&e? zhDr8R%v2Q|Zh)5F)~;C6()n*&(h6kEa>Rx;?P#`<`7GSRoxni$TYBgH{XJ`&P6H5& z4N%d($4Gsf{O7Ouud(~1@4oMjLVi3y{u1UY7c)pFk;2T6z2%hNbe1XDaPpart=OUE zscdH$dep8~&wW5H{8l7qJ2r0&Oy95!myt-lmM^x7ZaAClX(kudI?Gx-nAMW92M0zC zFLr5V3~WR61q>(o)n>5+KJOVe>^U?Q2vMeusg2A&S*>|y9y-i%6j7a!ziY7MpOBsd z-}>a?&A0BzFVY!S8s}MRt5dGtUDa5ZIqwO!zVuieYdXugn$=Q_c2+4;!N23pXaJRY zyuX6LigacBeFK}lQc!b{g5JgZF^Dd3 z<%orYeKl?7Mxu|C(#Mx&*dKjL2jo9pPELG3t8c1lpHPZy*$ zl|_{nwfT9`M=2S9xv#SuHmT_SQc8~I+E&%A25yw&PLhyaMnovv9_cC8Mh$Xv9frzt zIekmPhNeE`y=%OQ$<6EQOuciwAMa$lR~{$8`)oTLRWa-l*`enr^s7BCX6eT!;eicg zb*j!#I=P*XS(hBv63VYl-;&0)_9Idgig_LJpjk(QYYb}-`trt=(n(rl?EkqAC#`pShaq=AU z6@N=qUCf~5r=oZ9)>QJeEzC$wP^z(p-0dit#++L*t zWq!0$X6(TMA?A$6UKO9l4qw{CWS&fa^<*lU^npYD_? zjRdJH6+E^JG>X)Oi|9H8+(J2is^+(Tg}*N-Eo&a_!vV*CCdsYC5GaM zY4MNHP4G5XvP+|2dy`~^{p|W;q}oZa{si`@NA%Bm9@3i>cGH4${psyIw-c+56ED-R zwwn(mCKZzZuIfivF@DSID$$p>MAfkAOCO(YIRVE~oJ>MT9Ya>+6&Iq>1H9wRWs8P` zYnPo&!XDf;ee48B%HxSO8^|gA#($-gw~4n)k3OeS$V7Kor*Z+PRNg=R>`cIj*`IwM zF}GqG1w1QZO<-j|BN^O<^ls_;CZ^ul!UNGz0MVxoLY^ zS;_p8{NaPgY-PN!ycE`Agn^7-%XkH_w8TrGlcs&WKa!-^^Osx@osj?5=Fmepsw zL@&_fD5hp@Oxj|rWKoG9zZ$75nGM_V1c_YHQTP+zjF=UQND496qN3M8@l_xQ0-G3+ z-On@9E(7*4qu)rG%_<^~SPPH}y(k9*no{mK;fWo$YrgsEf${>2$2+i&)+tPfZ6xsn zgeQc@v2^uOuG}!X)X2hxLdBBajzc(Ss8OK^%k&4u6h7KlfZ2PdAvR#@K%Khq?vL$j zVA0gC+r7gJN~%3hU!y*-%}k&iaPDY&z3HlMW@X_n$0F`F&or>Nq%dl~@-DZk`o*ov z7?2?E@2POXx{yJ`(6Sq9E{4A^h4C}xHHhe`f`?iGl!nbZC7|rWo_}~g*Oxsfy*n&HkELFA#RFP!$%L64K|ti( zPn;9?b~6g6(lg*~WaRaw|9lMeEB#OkQP!cIr?0=k%e_Fxh54qH+(LDGtS)}VsOjbh zCGkLa{{t#c&qnw({IQ@$@#afnry#p-l}ho!4d=*FbK_ctLKm{JTlEo zEKugWsJL3K%TH78nYgotI$i@@>?ikRP()hTg+P{Af3MTkVsdJsd`UJO<^{iK#!?%> zpDhZ@G#INokn=8VydZP4FUgG39Eq`O)vZSR>c(R~t9|&FXWI~`^9puJDREPg6{GSs zP}Qs#DrIEf@Ik?{E4Cfk1odwOO2ntfLpLNSr;E}DBUjZ7`$wz_N(&y;H+OCbtc&rY zK+6FN7Ztye%FBDesK>UVV=Ftlw_m5PUfnOMgG50^ayJn8#F_fJs8OR?Uj99QpQ$r# z`vqg4)phe2+Yp-aiX>c_(IsQ0#2323IwMrh%v7^3jb>u<1mzV4#mcpF7oz%;K-CoN zLk0M#DhG5mt;E^XxJ5k9K9bbjw6JUMCXqU&Ub4f}#Buz?z3Mj-PqR?Z8*S1^1JE}> z;qH<}Q~;KxLu9QtIvJZt&tdVbQ#C=n^APVXOV(Qzz@r?s4EKU=Flf z!(|-t3FA{VF);uqJy|*eL-s^cRRlVaW+dusuK#Zu%jdt5UbV?yYp-)@)TRZK#P_>% z5Q(VMju0Wc=|p>H=&%r2vThH7%ul8p8?^|hT*e|Y9u`kmIA~|c} zGi{lCJ*LhogtgIJv7(gNXOHnGW);G|kb_H^Z$V#zEdAyt*I1ScNvt_mK&|Hb8 z&t;mHs}Nc%QlpZpAtO_|@KgZrPVm@g`Wz5YW`e;k7h<%$S)VPqp{l#za+j*(cL>YL z`N05UY*;W{O8HQVGI{RL95Zp5qPeN(HeN#1ss72r!GJ$ZvgK5hP{V`+kV>!KCo(Eq zP-J!2HL*@vcw?cGZjHWt6^L_5G{SN}@K*e1{jbkuT{>FZ!pPTtJNCE1WmYyT0?o=E z>1O~(NsruRq(84TO5go2uk_D@ipiV}f93lAviQOh8 z<2#9PpQx>r0JD55&-r_)86!6EImO=#f0-xx{v@z+dZtnMx@RT7SzXXPabknYs|+k; zTGPY2Qse&4&2YMi!C+~V`@Nt7Q@Hzu$jgjHn|uF9p3Msgq`*d_ZTM#)Bx&yiWOX$j4e*QLqpTbSLe$92u(y*^AN@G+b00Y`EO;xm9ay`ZVX?ZWe_J zyGr z$0}1uFTdB`TMCVpU}?+MGjNf@nEe%5Y)V007|f27bo`ArTA+rCmgXS2u`W8VosWCB z@AL}WiiBoJSm7$<6q4jbKI&*ga}-`jFT~t^Vwb7Kt<@@ew+9bf{YU=zUK=Xz^S>wa!9#d6oqi zyypf=Vfvq$c}e2ysqe=rpO(ChHJyr<#Rwt2zPkI3^l52YU2E&=xV=wblt8J)R%Pch&OZXOXtsf-KHG5 z3&b*z@(a?4sP@)=>`o*mcVw7p`}!;TzR#SO_f)8=+wuBzlG+yrGe46GdGz%$+YMyH zO1V|-XwU$N$D^TLoV{uB($8q-A{9fkrlN(ufKz5<*MV69Ty8DdC;uLErY05#>DipL zsc6D0()^Kp(kFQWAA2t;#Md$l^k{TRUutWA;8r_XK^~+fp>HuRq5f>TU@33}(`>Y2 zwbc-`SJ>T%JUpQTI%hB;%CN0ns&`+no4e~?I8BrTO*1cRf2wInL}Fp?JmGo_pJDzh zWb~iM1)eqiRLg(OI?FOm#-E^2t5=h`VR}>)3M-3n4&jHTs$jxJGSbYY$Fuc0I3EM+ zUk!WHlJfjw)l^q*0^wP1MRXGE)6prxjRgB6T|OkUL*e6M{nU@dxvGG7_B@5x%vCH* zdtBm4A(t=FU!rqeM#O3N3*&4v{j?sRZeE7VKK4J>8(IEF`ux^T_aDFme?<}eN8?{$ z3I4vm7zZy}9z2I_rD|ZKb}gCQ)Yht&Br4vk(nFgY=&WhFw4P3anmvuBjFeFmfrYht zj!A3f(yP|&1+Oxz26(NmU4ux}vg^ha)x#|C4hBxux>}^dyswpiQAO^JSG*_&XZTUO z%%0OtU)FlRwDAWe{6`g3XhXC=>w5JQvs_!8|UMPlDt^;Vhs@OH#T#K2rbvEE>c@wT8X{qXat znxn(BJ5o;fx=Op`2jR!O7E#uW2gtx|0j_DlRrHaF|B|`mq2mGHmE$irw1I!{um3;h z#lP)tN6dqa-Hrf|MC3jek&CqlPwTlg%-y&kCE8+d*wl=cz2ZQ`%tf8Uh^BmldCiL0 zyR9{wrNcVcyiJcCoK;z|FwDr6o*dl*R81;;sqdtm>hJfMhd;xo9JMt_+-v8eF8F1xvv~f$*HDWg+Jg zyalfSnmaRok`j>t;tP(){fnKMnLsJ3>3qKC*^`AXO;yvMDq4e#N8(wcW-jjds_CNz z-T+IX3vwj`MNlo2bq z)HznMzMtfuSsU%f_V@j5e|-XPQUfitPB*&n9+{mu^tUsq zq6vwA*=)y>xlsO=yjf=)acn|2YCUxFPI|mA4%->DpK93&^q(jkKoHk#;axbg1jNqy z{NFlTfO&nfBL}95)jKdta2fu|7>eS=;&>8dWSs_xW!$hS7uACT%AS?hF##EWxfq>1 zKmr&(}1ngKtsjR zh$L><=5u_EpwWs9jFU8`CQX0Y>C2d#Hl4KgAAH2qsTuCsPl+XTB}sNr0ACTF$Y;Okl;J8j`-exz32N7J`bG= z`AAv0?WKFIU{ezd*{4}%#%&;yItn$XX`$abahAs6GAJh5T zo?hY6(+V&XCtm9@IClVCW51Rjzg*P+i3w0)=0E&@w@nlFUyYW*<;$!SpDhMZD10$c+dt+OWi`ZqHO4eRbSoVPPJz}+MufpS+yh`YXnu;|6 z^MN8t5BQwD*Qr^EiI%T0nr&|?j9V=xSlNLHPjYk}xAx(;axH1D(c$&b zX9Js5Dhad)DX+`+A)(wRy1l79IR0t!og^dUUS}Rk{A~i-;C(P~aJlMm3%UvXjl|-T zHZ^T=uT($HhfOLjmH+03qrT65@z}CgBm{zA+Y?;`j8-B<{oelJG@eJZr%v4kW3!u4 zoIb3E4?MSgqOHzYyyz7@z*D#PmCT{TIUuG~bJo)cbfA%7kONLR zuQECtH}53q9Qjo%#`z(vGq7aOye+u&Hge?Xx++ssp9_}Shsy0>*=DO#Z|==~%?AWh zw}fF1q^r})^wNQ>^e1~@ad60I@cY(WKvbBG{~B*sq!EhA3{CZ>k4vtK*mUPRi>vhs zK)-hFWpW=XPk?FqcmF6UW~Ro7@_yEE;}BQI_X@`5_Iluk0Nbrwh2{-DBPkt(tfSGa zpBTk=MkvNJ+%_atpL%!Nicny)4wkT#y)L5)$yD7weIK)IxZdXvM)zW0b;>)k$mhfv zkiMM&oNZJtjxSlSg$Jfc<7UMym;|wW_sbm{g>Hvk4?^6+W=tJ!Q8dObVz7@LOI(}m zH(7P}Kpqb2n9r3|8#Gl1g|82vdv`2(%VoRwefx|PU8C}mUu)5`ti(X~D9*66G`P<+ zv~y+<5yH0>Yr*TaUWVE7@86E;`E68~bkLYs*QNBcCD!ddf4wF~ zDu(ib6kWs1aXQB!yZe$*&4qQu?VwD6$TFs-QXW$$Q>X;SIYdGs3a&j+>T~Qi?Qj}( zd?ICy@DI)uMODueez@~3MTC~aVivBN;yyhj{_wV8zQ_0B&mQ`^Pt2QJx~p#mH0MgI zBjT1fAV$?2n6Y?QoKcpYD#2q|BA2In_0Ih){R^)`JI>Ijc-na87=>a36(+)htISsL zr3%4Fg(bwwo|9vu9LL4S~doZV1{ln{0qET{XaJRe^=dErdhUl6?IW^1xwKQ z9rPIe6&(~6?AMd?-T!fq zB;2iE2+eOEXla%CknIfjv`S$Ge zSiJy{Oa;dnm{;2J1jF{G8FVvKh$fo(wFPi6m8DY8kC(WcCYYV)8Sq3^kxUhD*R!?M zL5#n8_IR_XV|jMw6Nfc?pX`1Uz>~AC>c)+VJ><~7nPQ|ju`(3y*<|hGYkL?7pNmXQ zj(kBGC*h>!M{L+lV14GmW9XBD{Yb&Jn^@0Kd*s9877;Q6F|Jb1Qb{ev3V(P8u?wd+W& z0N~RQnE*Xj9aB3^)q47;pFKn7iI(d2&3a9#-?`ITYO$}-y^9#z`4t4yuOc}3@&$+Q zaAPXSMl`l{;HB>%W|B@Yhu*k(3^~$Uw)OOn&`z=(b)jDQ4GqEf9V~ib>io(LGoEBa zIp-m)!O)ivKX{9?TPVU5-$f_Sj6gBn*iZ|=@ipy^GMb4XsY99|&6I>Xmchukv_scA zZ{H5G&?JZ@kz&gmuQaQ@x%^C}v~&x>Zg(c1*UdJ0ihSa6;oDP?7P;mN=`Nd}x$X zj;{`uh-p@3%?TM7Uj<6JLV;AD0rMaxmD+~Y$Bs4nN{ia3GTq1#!j0o~Al>VY_;5{J zW3I(ndff?z3K1Oese@sg{R;+7I^>{;ZEkdHk|zvd?yX7P)}*Io3+kY25Kc{MwYh(-Iwv=o>-{b>)a}KjqDb02V(swa@TP$-M~TbI%XhdZb9G(Jl;)`Y|oD# zoC`}37A&N3MJ`aT>bulkCfYa0)YV?H_XU#f$ADrhO4wqwgrjG`HnGlm0zsNR;p@+c z!#{B`XqLmj&GN&wNKZ|xBoC^}INC_%>zBf@5(t&?cU7oJMhOqE1Wi#W&DBcRpD5Qb zu}3wOo0arJU>^jZs(twMu`+}$(Nf&6e*pV+`Z=1c6fP~Z`?Bs0i8`nj9&{N=XSwCk z)j8WxY*=GA899bZ9fnKLZHq?|6hRX45t@GQsg*07g42d5-U32Su1k}4InrrxsOuv; z;Jc5g?7=*#dZI{g>507fE&?-^6>{n(MWMV!XW$!7TJ0U07%knc%1+yh;9D`xAdUuavxGv>iq!gsw2n35DcTzx$G&@=-IH_uUsL4QdQWz~n*gd1H6ri6Ys znPA~>!%Mw1&ts_?FkJ35cMPkSkCoN>kb&(VM?oZSxms?W@SY}WLYlkAVsY(wm7pHZD(>${Y)VK z$%|*}B-l!yU&Iz*`sqZ$^t@9)Au>mCBQMrbA}elq^kbm|gh$LY>qnnH%^9m@%(NA7 zwt4+}-w9taxVbJqs0$;TAjMPe(d8}QFo`E5Oh*$ma}sCH_#T3l?$DUUV9s4@t@G~J zHAJ~ueyNl?oxK20CWU1gI|7OaP`0dFrxnT~3?fMpeC%Qg%hJ(RFoo&1$ck#v?-F9` z*R^ZzYnx^dPu2;Bd69MG;REc56JIG-D%>=V=VvxDZxY37&zVFl_)yz4eaDp4t$iI~ zN1hx39aO>I<)YRqn$4fgJt?SY=kdW`yv%Tz>4K$FC^}*rF@O{nxM&jy20wm#KS8(_ z(YCt5A(+XP%d(ReG7!Mh?t-x-(-78!@_%i=exB|k_U_uWwn literal 0 HcmV?d00001 diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index de917b7..88164bd 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -826,6 +826,7 @@ class ApiConsts { static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; + static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; // Ancillary Order Apis static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 5fccc6e..b34b47e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -1,6 +1,7 @@ class AppAssets { static const String svgBasePath = 'assets/images/svg'; static const String pngBasePath = 'assets/images/png'; + static const String jpgBasePath = 'assets/images/jpg'; static const String hmg = '$svgBasePath/hmg.svg'; static const String arrow_back = '$svgBasePath/arrow-back.svg'; @@ -206,6 +207,9 @@ class AppAssets { static const String dummy_user = '$pngBasePath/dummy_user.png'; static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; + + // JPGS // + static const String report = '$jpgBasePath/hmg_logo.jpg'; } class AppAnimations { diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index d4afbd9..c167aa0 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -55,6 +55,8 @@ import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../features/active_prescriptions/active_prescriptions_repo.dart'; +import '../features/terms_conditions/terms_conditions_repo.dart'; +import '../features/terms_conditions/terms_conditions_view_model.dart'; GetIt getIt = GetIt.instance; @@ -121,6 +123,10 @@ class AppDependencies { getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), + ), + ); // ViewModels // Global/shared VMs → LazySingleton diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart index 42faafa..dc859a9 100644 --- a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -1,165 +1,78 @@ -import 'dart:convert'; class ActivePrescriptionsResponseModel { - dynamic address; - int? appointmentNo; - dynamic clinic; - dynamic companyName; - int? days; - dynamic doctorName; - int? doseDailyQuantity; // doses per day + String? itemId; + String? itemDescription; + String? route; String? frequency; int? frequencyNumber; - dynamic image; - dynamic imageExtension; - dynamic imageSrcUrl; - String? imageString; - dynamic imageThumbUrl; - dynamic isCovered; - String? itemDescription; - int? itemId; + int? doseDailyQuantity; + int? days; + String? startDate; + String? endDate; String? orderDate; - int? patientId; - dynamic patientName; - dynamic phoneOffice1; - dynamic prescriptionQr; - dynamic prescriptionTimes; - dynamic productImage; - String? productImageBase64; String? productImageString; - int? projectId; - dynamic projectName; - dynamic remarks; - String? route; - String? sku; - int? scaleOffset; - String? startDate; - - // Added for reminder feature + bool isReminderOn; List selectedDoseTimes = []; - bool isReminderOn = false; // toggle status ActivePrescriptionsResponseModel({ - this.address, - this.appointmentNo, - this.clinic, - this.companyName, - this.days, - this.doctorName, - this.doseDailyQuantity, + this.itemId, + this.itemDescription, + this.route, this.frequency, this.frequencyNumber, - this.image, - this.imageExtension, - this.imageSrcUrl, - this.imageString, - this.imageThumbUrl, - this.isCovered, - this.itemDescription, - this.itemId, + this.doseDailyQuantity, + this.days, + this.startDate, + this.endDate, this.orderDate, - this.patientId, - this.patientName, - this.phoneOffice1, - this.prescriptionQr, - this.prescriptionTimes, - this.productImage, - this.productImageBase64, this.productImageString, - this.projectId, - this.projectName, - this.remarks, - this.route, - this.sku, - this.scaleOffset, - this.startDate, - - // ✅ Default values for new fields (won’t break API) - List? selectedDoseTimes, this.isReminderOn = false, - }) : selectedDoseTimes = selectedDoseTimes ?? []; - - factory ActivePrescriptionsResponseModel.fromRawJson(String str) => - ActivePrescriptionsResponseModel.fromJson(json.decode(str)); - - String toRawJson() => json.encode(toJson()); - - factory ActivePrescriptionsResponseModel.fromJson(Map json) => - ActivePrescriptionsResponseModel( - address: json["Address"], - appointmentNo: json["AppointmentNo"], - clinic: json["Clinic"], - companyName: json["CompanyName"], - days: json["Days"], - doctorName: json["DoctorName"], - doseDailyQuantity: json["DoseDailyQuantity"], - frequency: json["Frequency"], - frequencyNumber: json["FrequencyNumber"], - image: json["Image"], - imageExtension: json["ImageExtension"], - imageSrcUrl: json["ImageSRCUrl"], - imageString: json["ImageString"], - imageThumbUrl: json["ImageThumbUrl"], - isCovered: json["IsCovered"], - itemDescription: json["ItemDescription"], - itemId: json["ItemID"], - orderDate: json["OrderDate"], - patientId: json["PatientID"], - patientName: json["PatientName"], - phoneOffice1: json["PhoneOffice1"], - prescriptionQr: json["PrescriptionQR"], - prescriptionTimes: json["PrescriptionTimes"], - productImage: json["ProductImage"], - productImageBase64: json["ProductImageBase64"], - productImageString: json["ProductImageString"], - projectId: json["ProjectID"], - projectName: json["ProjectName"], - remarks: json["Remarks"], - route: json["Route"], - sku: json["SKU"], - scaleOffset: json["ScaleOffset"], - startDate: json["StartDate"], + List? selectedDoseTimes, + }) { + this.selectedDoseTimes = selectedDoseTimes ?? []; + } - // ✅ Ensure local reminder values are not overwritten by API - selectedDoseTimes: [], - isReminderOn: false, - ); + /// ========== JSON FROM ========== + factory ActivePrescriptionsResponseModel.fromJson(Map json) { + return ActivePrescriptionsResponseModel( + itemId: json["ItemID"]?.toString() ?? "", + itemDescription: json["ItemDescription"] ?? "", + route: json["Route"] ?? "", + frequency: json["Frequency"] ?? "", + frequencyNumber: json["FrequencyNumber"], + doseDailyQuantity: json["DoseDailyQuantity"] ?? 1, + days: json["Days"] ?? 0, + startDate: json["StartDate"] ?? "", + endDate: json["EndDate"] ?? "", + orderDate: json["OrderDate"] ?? "", + productImageString: json["ProductImageString"] ?? "", + isReminderOn: json["IsReminderOn"] == true, + selectedDoseTimes: + (json["SelectedDoseTimes"] as List?) + ?.map((e) => e?.toString()) + .toList() ?? + [], + ); + } - Map toJson() => { - "Address": address, - "AppointmentNo": appointmentNo, - "Clinic": clinic, - "CompanyName": companyName, - "Days": days, - "DoctorName": doctorName, - "DoseDailyQuantity": doseDailyQuantity, - "Frequency": frequency, - "FrequencyNumber": frequencyNumber, - "Image": image, - "ImageExtension": imageExtension, - "ImageSRCUrl": imageSrcUrl, - "ImageString": imageString, - "ImageThumbUrl": imageThumbUrl, - "IsCovered": isCovered, - "ItemDescription": itemDescription, - "ItemID": itemId, - "OrderDate": orderDate, - "PatientID": patientId, - "PatientName": patientName, - "PhoneOffice1": phoneOffice1, - "PrescriptionQR": prescriptionQr, - "PrescriptionTimes": prescriptionTimes, - "ProductImage": productImage, - "ProductImageBase64": productImageBase64, - "ProductImageString": productImageString, - "ProjectID": projectId, - "ProjectName": projectName, - "Remarks": remarks, - "Route": route, - "SKU": sku, - "ScaleOffset": scaleOffset, - "StartDate": startDate, - }; + /// ========== JSON TO ========== + Map toJson() { + return { + "ItemID": itemId, + "ItemDescription": itemDescription, + "Route": route, + "Frequency": frequency, + "FrequencyNumber": frequencyNumber, + "DoseDailyQuantity": doseDailyQuantity, + "Days": days, + "StartDate": startDate, + "EndDate": endDate, + "OrderDate": orderDate, + "ProductImageString": productImageString, + "IsReminderOn": isReminderOn, + "SelectedDoseTimes": selectedDoseTimes, + }; + } } diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart index d5180ae..6c24998 100644 --- a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -10,6 +10,7 @@ class HmgServicesComponentModel { Color bgColor; Color textColor; String route; + bool isExternalLink; HmgServicesComponentModel( this.action, @@ -21,5 +22,6 @@ class HmgServicesComponentModel { this.bgColor = Colors.white, this.textColor = Colors.black, this.route = '', + this.isExternalLink = false, }); } diff --git a/lib/features/terms_conditions/terms_conditions_repo.dart b/lib/features/terms_conditions/terms_conditions_repo.dart new file mode 100644 index 0000000..a5d3f95 --- /dev/null +++ b/lib/features/terms_conditions/terms_conditions_repo.dart @@ -0,0 +1,60 @@ +import 'package:dartz/dartz.dart'; +import '../../core/api/api_client.dart'; +import '../../core/api_consts.dart'; +import '../../core/exceptions/api_failure.dart'; +import '../../services/logger_service.dart'; + +abstract class TermsConditionsRepo { + Future> getTermsConditions(); +} + +class TermsConditionsRepoImp implements TermsConditionsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + TermsConditionsRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future> getTermsConditions() async { + Failure? failure; + String? html; + + try { + await apiClient.post( + ApiConsts.getTermsConditions, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType ?? ServerFailure(error.toString()); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + + final content = response['UserAgreementContent']; + + if (content is String && content.isNotEmpty) { + html = content; + } else { + failure = DataParsingFailure( + 'UserAgreementContent is null or not String'); + } + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + } catch (e) { + failure = UnknownFailure(e.toString()); + } + + if (failure != null) return Left(failure!); + if (html == null || html!.isEmpty) { + return Left(ServerFailure('No terms and conditions returned')); + } + + return Right(html!); + } +} + diff --git a/lib/features/terms_conditions/terms_conditions_view_model.dart b/lib/features/terms_conditions/terms_conditions_view_model.dart new file mode 100644 index 0000000..5d67ae7 --- /dev/null +++ b/lib/features/terms_conditions/terms_conditions_view_model.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class TermsConditionsViewModel extends ChangeNotifier { + final TermsConditionsRepo termsConditionsRepo; + final ErrorHandlerService errorHandlerService; + + String? termsConditionsHtml; + bool isLoading = false; + + TermsConditionsViewModel({ + required this.termsConditionsRepo, + required this.errorHandlerService, + }); + + Future getTermsConditions({ + Function()? onSuccess, + Function(String)? onError, + }) async { + isLoading = true; + notifyListeners(); + + final result = await termsConditionsRepo.getTermsConditions(); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + isLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.message ?? 'Something went wrong'); + } + }, + (html) { + termsConditionsHtml = html; + isLoading = false; + notifyListeners(); + if (onSuccess != null) onSuccess(); + }, + ); + } +} + + diff --git a/lib/main.dart b/lib/main.dart index f127400..e9ceec7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,6 +38,7 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'core/utils/size_utils.dart'; +import 'features/terms_conditions/terms_conditions_view_model.dart'; import 'firebase_options.dart'; @pragma('vm:entry-point') @@ -146,9 +147,12 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), - ) + ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index d27b35d..d0720fb 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -547,7 +547,7 @@ class _ActiveMedicationPageState extends State { AppColors.textGreenColor, AppColors.infoColor, AppColors.labelColorYellow, - AppColors.purpleBg + AppColors.mainPurple ][doseIndex % 4]; final doseLabel = "${doseIndex + 1}${_getSuffix(doseIndex + 1)}"; diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index af576aa..bc63876 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_s import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:url_launcher/url_launcher.dart'; class ServicesPage extends StatelessWidget { ServicesPage({super.key}); @@ -41,8 +42,30 @@ class ServicesPage extends StatelessWidget { textColor: AppColors.blackColor, route: AppRoutes.homeHealthCarePage, ), + HmgServicesComponentModel( + 12, + "Latest News".needTranslation, + "".needTranslation, + AppAssets.news, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: "https://twitter.com/HMG", + isExternalLink: true, + ), + HmgServicesComponentModel( + 12, + "Monthly Reports".needTranslation, + "".needTranslation, + AppAssets.report_icon, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: AppRoutes.monthlyReports, + ), ]; + @override Widget build(BuildContext context) { return CollapsingListView( @@ -72,7 +95,7 @@ class ServicesPage extends StatelessWidget { return ServiceGridViewItem(hmgServices[index], index, false); }, ), - ) + ), ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 225bd96..3a0c211 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -5,6 +5,7 @@ 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/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:url_launcher/url_launcher.dart'; class ServiceGridViewItem extends StatelessWidget { final HmgServicesComponentModel hmgServiceComponentModel; @@ -12,12 +13,18 @@ class ServiceGridViewItem extends StatelessWidget { final bool isHomePage; final bool isLocked; - const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false}); + const ServiceGridViewItem( + this.hmgServiceComponentModel, this.index, this.isHomePage, + {super.key, this.isLocked = false}); @override Widget build(BuildContext context) { return InkWell( - onTap: () => getIt.get().pushPageRoute(hmgServiceComponentModel.route), + onTap: () => hmgServiceComponentModel.isExternalLink + ? _openLink(hmgServiceComponentModel.route) + : getIt + .get() + .pushPageRoute(hmgServiceComponentModel.route), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, @@ -47,4 +54,14 @@ class ServiceGridViewItem extends StatelessWidget { ], )); } + + Future _openLink(String link) async { + final Uri url = Uri.parse(link); + + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + throw "Could not launch $url"; + } + } } diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart new file mode 100644 index 0000000..97cc0e3 --- /dev/null +++ b/lib/presentation/monthly_reports/monthly_reports_page.dart @@ -0,0 +1,283 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/presentation/monthly_reports/user_agreement_page.dart'; + +import '../../generated/locale_keys.g.dart'; +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/input_widget.dart'; + +class MonthlyReportsPage extends StatefulWidget { + const MonthlyReportsPage({super.key}); + + @override + State createState() => _MonthlyReportsPageState(); +} + +class _MonthlyReportsPageState extends State { + bool isHealthSummaryEnabled = false; + bool isTermsAccepted = false; + + final TextEditingController emailController = TextEditingController(); + + @override + void dispose() { + emailController.dispose(); + super.dispose(); + } + + void _showError(String message) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + behavior: SnackBarBehavior.floating, + ), + ); + } + + void _onSavePressed() { + if (!isTermsAccepted) { + _showError("Please accept the terms and conditions".needTranslation); + return; + } + + final email = emailController.text.trim(); + if (email.isEmpty) { + _showError("Please enter your email".needTranslation); + return; + } + + setState(() { + isHealthSummaryEnabled = true; + }); + + // TODO: هنا حطي API/logic حق الحفظ + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Monthly Reports".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 27.f, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 16.h), + + Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + height: 54.h, + alignment: Alignment.center, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: (12.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Patient Health Summary Report".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + _buildToggle(), + ], + ), + ), + + SizedBox(height: 16.h), + + TextInputWidget( + controller: emailController, + labelText: "Eamil*".needTranslation, + hintText: "email@email.com", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.emailAddress, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + onChange: (value) { + setState(() {}); + }, + ).paddingOnly(top: 8.h, bottom: 8.h), + + Row( + children: [ + Text( + "To View The Terms and Conditions".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + InkWell( + child: Text( + "Click here".needTranslation, + style: TextStyle( + color: AppColors.errorColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const UserAgreementPage(), + ), + ); + }, + ), + ], + ), + + SizedBox(height: 12.h), + + GestureDetector( + onTap: () => setState(() => isTermsAccepted = !isTermsAccepted), + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted + ? AppColors.textGreenColor + : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: isTermsAccepted + ? AppColors.lightGreenColor + : AppColors.greyColor, + width: 2.h, + ), + ), + child: isTermsAccepted + ? Icon(Icons.check, size: 16.f, color: Colors.white) + : null, + ), + SizedBox(width: 12.h), + Text( + "I agree to the terms and conditions".needTranslation, + style: context.dynamicTextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + color: const Color(0xFF2E3039), + ), + ), + ], + ), + ), + + SizedBox(height: 12.h), + + Text( + "This is monthly health summary report".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 10.f, + fontWeight: FontWeight.w600, + ), + ), + + SizedBox(height: 12.h), + + Image.asset('assets/images/jpg/report.jpg'), + + SizedBox(height: 16.h), + + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.successColor, + foregroundColor: AppColors.whiteColor, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: _onSavePressed, + child: Text( + LocaleKeys.save.tr(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.f, + ), + ), + ), + ), + ], + ), + ], + ), + ).paddingAll(16), + ); + } + + Widget _buildToggle() { + final value = isHealthSummaryEnabled; + + return AbsorbPointer( + absorbing: true, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 50.h, + height: 28.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: value + ? AppColors.lightGreenColor + : AppColors.greyColor.withOpacity(0.3), + ), + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + alignment: value ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(3), + child: Container( + width: 22.h, + height: 22.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: value + ? AppColors.textGreenColor + : AppColors.greyTextColor, + ), + ), + ), + ), + ), + ); + } +} + + diff --git a/lib/presentation/monthly_reports/user_agreement_page.dart b/lib/presentation/monthly_reports/user_agreement_page.dart new file mode 100644 index 0000000..f6379ad --- /dev/null +++ b/lib/presentation/monthly_reports/user_agreement_page.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_view_model.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; + +class UserAgreementPage extends StatefulWidget { + const UserAgreementPage({super.key}); + + @override + State createState() => _UserAgreementPageState(); +} + +class _UserAgreementPageState extends State { + late final WebViewController _webViewController; + bool _isLoading = true; + String? _errorMessage; + + @override + void initState() { + super.initState(); + + _webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onPageStarted: (_) { + setState(() { + _isLoading = true; + }); + }, + onPageFinished: (_) { + setState(() { + _isLoading = false; + }); + }, + onWebResourceError: (error) { + }, + ), + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final vm = + Provider.of(context, listen: false); + + vm.getTermsConditions( + onSuccess: () { + final htmlString = vm.termsConditionsHtml ?? ''; + + if (htmlString.isNotEmpty) { + setState(() { + _errorMessage = null; + _isLoading = true; + }); + _webViewController.loadHtmlString(htmlString); + } else { + setState(() { + _isLoading = false; + _errorMessage = 'لا توجد شروط متاحة حالياً'.needTranslation; + }); + } + }, + onError: (msg) { + setState(() { + _isLoading = false; + _errorMessage = msg; + }); + }, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: Stack( + children: [ + WebViewWidget(controller: _webViewController), + + if (_errorMessage != null) + Center( + child: Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _errorMessage!, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + if (_isLoading) + const Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + } +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index a0ee1e5..7330744 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; +import '../presentation/monthly_reports/monthly_reports_page.dart'; + class AppRoutes { static const String initialRoute = '/initialRoute'; static const String loginScreen = '/loginScreen'; @@ -19,7 +21,7 @@ class AppRoutes { static const String eReferralPage = '/erReferralPage'; static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; static const String homeHealthCarePage = '/homeHealthCarePage'; - + static const String monthlyReports = '/monthlyReportsPage'; static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -29,6 +31,7 @@ class AppRoutes { medicalFilePage: (context) => MedicalFilePage(), eReferralPage: (context) => EReferralPage(), comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), - homeHealthCarePage: (context) => HhcProceduresPage() + homeHealthCarePage: (context) => HhcProceduresPage(), + monthlyReports: (context) => MonthlyReportsPage() }; } diff --git a/pubspec.yaml b/pubspec.yaml index 3d6604c..0de3829 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,7 +79,7 @@ dependencies: path_provider: ^2.0.8 open_filex: ^4.7.0 flutter_swiper_view: ^1.1.8 - + webview_flutter: ^4.9.0 location: ^8.0.1 gms_check: ^1.0.4 huawei_location: ^6.14.2+301 From c9de23347a69da4ad2ae3bbcf559e94752cee5cc Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Mon, 15 Dec 2025 12:30:42 +0300 Subject: [PATCH 07/46] fix error --- lib/core/app_assets.dart | 15 ++++++++++++--- lib/core/dependencies.dart | 2 +- lib/core/utils/calender_utils_new.dart | 1 + .../ui_models/hmg_services_component_model.dart | 3 ++- lib/presentation/hmg_services/services_view.dart | 6 +++--- lib/routes/app_routes.dart | 4 ++-- lib/splashPage.dart | 1 + pubspec.yaml | 1 - 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 4e5c7b8..40add26 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -1,7 +1,6 @@ class AppAssets { static const String svgBasePath = 'assets/images/svg'; static const String pngBasePath = 'assets/images/png'; - static const String jpgBasePath = 'assets/images/jpg'; static const String hmg = '$svgBasePath/hmg.svg'; static const String arrow_back = '$svgBasePath/arrow-back.svg'; @@ -235,6 +234,17 @@ class AppAssets { static const String heart = '$svgBasePath/heart.svg'; static const String alertSquare = '$svgBasePath/alert-square.svg'; static const String arrowRight = '$svgBasePath/arrow-right.svg'; + static const String tickIcon = '$svgBasePath/tick.svg'; + + // Symptoms Checker + static const String calendarGrey = '$svgBasePath/calendar-grey.svg'; + static const String weightScale = '$svgBasePath/weight-scale.svg'; + static const String rulerIcon = '$svgBasePath/ruler.svg'; + static const String genderIcon = '$svgBasePath/gender.svg'; + static const String bodyIcon = '$svgBasePath/body_icon.svg'; + static const String rotateIcon = '$svgBasePath/rotate_icon.svg'; + static const String refreshIcon = '$svgBasePath/refresh.svg'; + static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg'; // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; @@ -257,8 +267,6 @@ class AppAssets { static const String fullBodyFront = '$pngBasePath/full_body_front.png'; static const String fullBodyBack = '$pngBasePath/full_body_back.png'; - // JPGS // - static const String report = '$jpgBasePath/hmg_logo.jpg'; } class AppAnimations { @@ -280,3 +288,4 @@ class AppAnimations { static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json'; static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json'; } + diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 674cd02..a6699c6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -134,7 +134,7 @@ class AppDependencies { getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), - ), + ),); // ViewModels // Global/shared VMs → LazySingleton diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart index 5a43d78..5e9e91b 100644 --- a/lib/core/utils/calender_utils_new.dart +++ b/lib/core/utils/calender_utils_new.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:device_calendar_plus/device_calendar_plus.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:jiffy/jiffy.dart' show Jiffy; +import 'package:manage_calendar_events/manage_calendar_events.dart' hide Calendar; class CalenderUtilsNew { final DeviceCalendar calender = DeviceCalendar.instance; diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart index 5e531c7..ebc9511 100644 --- a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -9,7 +9,8 @@ class HmgServicesComponentModel { bool isLogin; bool isLocked; Color textColor; - String route; + Color bgColor; + String? route; bool isExternalLink; Function? onTap; diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 237b4cd..59efb73 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -19,16 +19,16 @@ class ServiceGridViewItem extends StatelessWidget { const ServiceGridViewItem( this.hmgServiceComponentModel, this.index, this.isHomePage, - {super.key, this.isLocked = false}); + {super.key, this.isLocked = false, required this.isHealthToolIcon, this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: () => hmgServiceComponentModel.isExternalLink - ? _openLink(hmgServiceComponentModel.route) + ? _openLink(hmgServiceComponentModel.route!) : getIt .get() - .pushPageRoute(hmgServiceComponentModel.route), + .pushPageRoute(hmgServiceComponentModel.route!), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index f32a003..81c5ee4 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -82,8 +82,8 @@ class AppRoutes { huaweiHealthExample: (context) => HuaweiHealthExample(), // - healthCalculatorsPage: (context) => HealthCalculatorsPage() - monthlyReports: (context) => MonthlyReportsPage() + healthCalculatorsPage: (context) => HealthCalculatorsPage(), + monthlyReports: (context) => MonthlyReportsPage() }; } diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 9aa16ee..2327ab5 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -35,6 +35,7 @@ import 'core/utils/local_notifications.dart'; import 'core/utils/push_notification_handler.dart'; import 'widgets/routes/custom_page_route.dart'; + class SplashPage extends StatefulWidget { @override _SplashScreenState createState() => _SplashScreenState(); diff --git a/pubspec.yaml b/pubspec.yaml index 9f0be5f..5590c9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,6 @@ dependencies: google_maps_flutter: ^2.13.1 flutter_zoom_videosdk: 2.1.10 dart_jsonwebtoken: ^3.2.0 - webview_flutter: ^4.9.0 dartz: ^0.10.1 equatable: ^2.0.7 google_api_availability: ^5.0.1 From 68f044de523a526e5f641b9eaa411dc25ebb9705 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 24 Dec 2025 09:54:39 +0300 Subject: [PATCH 08/46] added monthly report --- lib/core/dependencies.dart | 7 +- .../monthly_reports/monthly_reports_repo.dart | 96 +++++++++++++++++++ .../monthly_reports_view_model.dart | 33 +++++++ .../terms_conditions_repo.dart | 0 .../terms_conditions_view_model.dart | 2 +- lib/main.dart | 2 +- .../monthly_reports/monthly_reports_page.dart | 51 +++++++--- .../monthly_reports/user_agreement_page.dart | 8 +- lib/routes/app_routes.dart | 14 ++- 9 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 lib/features/monthly_reports/monthly_reports_repo.dart create mode 100644 lib/features/monthly_reports/monthly_reports_view_model.dart rename lib/features/{terms_conditions => monthly_reports}/terms_conditions_repo.dart (100%) rename lib/features/{terms_conditions => monthly_reports}/terms_conditions_view_model.dart (92%) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a6699c6..8cc29df 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart'; import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/monthly_reports/monthly_reports_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; @@ -48,6 +49,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -62,8 +64,8 @@ import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../features/active_prescriptions/active_prescriptions_repo.dart'; -import '../features/terms_conditions/terms_conditions_repo.dart'; -import '../features/terms_conditions/terms_conditions_view_model.dart'; +import '../features/monthly_reports/terms_conditions_repo.dart'; +import '../features/monthly_reports/terms_conditions_view_model.dart'; GetIt getIt = GetIt.instance; @@ -135,6 +137,7 @@ class AppDependencies { getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), ),); + getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton diff --git a/lib/features/monthly_reports/monthly_reports_repo.dart b/lib/features/monthly_reports/monthly_reports_repo.dart new file mode 100644 index 0000000..4ace6ec --- /dev/null +++ b/lib/features/monthly_reports/monthly_reports_repo.dart @@ -0,0 +1,96 @@ +import 'package:dartz/dartz.dart'; +import '../../core/api/api_client.dart'; +import '../../core/api_consts.dart'; +import '../../core/common_models/generic_api_model.dart'; +import '../../core/exceptions/api_failure.dart'; +import '../../services/logger_service.dart'; + +abstract class MonthlyReportsRepo { + Future>> saveMonthlyReport({ + String? email, + }); +} + +class MonthlyReportsRepoImp implements MonthlyReportsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + MonthlyReportsRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future>> saveMonthlyReport({ + String? email, + }) async { + try { + Failure? failure; + + GenericApiModel? reportApiResponse; + + await apiClient.post( + ApiConsts.getMonthlyReports, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + reportApiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (reportApiResponse == null) return Left(ServerFailure("Unknown error")); + + if ((reportApiResponse!.messageStatus ?? 0) != 1) { + return Right(reportApiResponse!); + } + + GenericApiModel? emailApiResponse; + + final Map emailRequest = {}; + + if (email != null && email.trim().isNotEmpty) { + emailRequest["Email"] = email.trim(); + } + + await apiClient.post( + ApiConsts.updatePatientEmail, + body: emailRequest, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + emailApiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (emailApiResponse == null) return Left(ServerFailure("Unknown error")); + + return Right(emailApiResponse!); + } catch (e) { + loggerService.logError("MonthlyReportsRepo.saveMonthlyReport error: $e"); + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/monthly_reports/monthly_reports_view_model.dart b/lib/features/monthly_reports/monthly_reports_view_model.dart new file mode 100644 index 0000000..4fd82da --- /dev/null +++ b/lib/features/monthly_reports/monthly_reports_view_model.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'monthly_reports_repo.dart'; +class MonthlyReportsViewModel extends ChangeNotifier { + final MonthlyReportsRepo monthlyReportsRepo; + final ErrorHandlerService errorHandlerService; + + bool isLoading = false; + + MonthlyReportsViewModel({ + required this.monthlyReportsRepo, + required this.errorHandlerService, + }); + + Future saveMonthlyReport({String? email}) async { + isLoading = true; + notifyListeners(); + + final result = await monthlyReportsRepo.saveMonthlyReport(email: email); + + final success = result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + return false; + }, + (apiResponse) => (apiResponse.messageStatus ?? 0) == 1, + ); + + isLoading = false; + notifyListeners(); + return success; + } +} diff --git a/lib/features/terms_conditions/terms_conditions_repo.dart b/lib/features/monthly_reports/terms_conditions_repo.dart similarity index 100% rename from lib/features/terms_conditions/terms_conditions_repo.dart rename to lib/features/monthly_reports/terms_conditions_repo.dart diff --git a/lib/features/terms_conditions/terms_conditions_view_model.dart b/lib/features/monthly_reports/terms_conditions_view_model.dart similarity index 92% rename from lib/features/terms_conditions/terms_conditions_view_model.dart rename to lib/features/monthly_reports/terms_conditions_view_model.dart index 5d67ae7..bd70b87 100644 --- a/lib/features/terms_conditions/terms_conditions_view_model.dart +++ b/lib/features/monthly_reports/terms_conditions_view_model.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_repo.dart'; +import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class TermsConditionsViewModel extends ChangeNotifier { diff --git a/lib/main.dart b/lib/main.dart index 801a54c..714053f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,7 +43,7 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'core/utils/size_utils.dart'; -import 'features/terms_conditions/terms_conditions_view_model.dart'; +import 'features/monthly_reports/terms_conditions_view_model.dart'; import 'firebase_options.dart'; @pragma('vm:entry-point') diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart index 97cc0e3..78c3f30 100644 --- a/lib/presentation/monthly_reports/monthly_reports_page.dart +++ b/lib/presentation/monthly_reports/monthly_reports_page.dart @@ -3,12 +3,15 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/monthly_reports/monthly_reports_view_model.dart'; import 'package:hmg_patient_app_new/presentation/monthly_reports/user_agreement_page.dart'; +import 'package:provider/provider.dart'; import '../../generated/locale_keys.g.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/input_widget.dart'; +import '../../widgets/loader/bottomsheet_loader.dart'; class MonthlyReportsPage extends StatefulWidget { const MonthlyReportsPage({super.key}); @@ -39,7 +42,25 @@ class _MonthlyReportsPageState extends State { ); } - void _onSavePressed() { + void _showSuccessSnackBar() { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Successfully updated".needTranslation, + style: const TextStyle( + color: AppColors.whiteColor, + fontWeight: FontWeight.w600, + ), + ), + behavior: SnackBarBehavior.floating, + backgroundColor: AppColors.textGreenColor, + duration: const Duration(seconds: 2), + ), + ); + } + + Future _onSavePressed() async { if (!isTermsAccepted) { _showError("Please accept the terms and conditions".needTranslation); return; @@ -51,11 +72,18 @@ class _MonthlyReportsPageState extends State { return; } - setState(() { - isHealthSummaryEnabled = true; - }); + final vm = context.read(); - // TODO: هنا حطي API/logic حق الحفظ + // LoaderBottomSheet.showLoader(); + final ok = await vm.saveMonthlyReport(email: email); + // LoaderBottomSheet.hideLoader(); + + if (ok) { + setState(() => isHealthSummaryEnabled = true); + _showSuccessSnackBar(); + } else { + _showError("Failed to update".needTranslation); + } } @override @@ -87,7 +115,7 @@ class _MonthlyReportsPageState extends State { height: 54.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, + color: AppColors.whiteColor, borderRadius: (12.r), ), child: Row( @@ -139,7 +167,7 @@ class _MonthlyReportsPageState extends State { child: Text( "Click here".needTranslation, style: TextStyle( - color: AppColors.errorColor, + color: AppColors.primaryRedColor, fontSize: 14.f, fontWeight: FontWeight.w600, ), @@ -179,7 +207,7 @@ class _MonthlyReportsPageState extends State { ), ), child: isTermsAccepted - ? Icon(Icons.check, size: 16.f, color: Colors.white) + ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor,) : null, ), SizedBox(width: 12.h), @@ -188,7 +216,7 @@ class _MonthlyReportsPageState extends State { style: context.dynamicTextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, - color: const Color(0xFF2E3039), + color: AppColors.textColor, ), ), ], @@ -198,7 +226,8 @@ class _MonthlyReportsPageState extends State { SizedBox(height: 12.h), Text( - "This is monthly health summary report".needTranslation, + "This monthly Health Summary Report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as an official report so no medical decisions should be taken based on it" + .needTranslation, style: TextStyle( color: AppColors.textColor, fontSize: 10.f, @@ -279,5 +308,3 @@ class _MonthlyReportsPageState extends State { ); } } - - diff --git a/lib/presentation/monthly_reports/user_agreement_page.dart b/lib/presentation/monthly_reports/user_agreement_page.dart index f6379ad..73ea564 100644 --- a/lib/presentation/monthly_reports/user_agreement_page.dart +++ b/lib/presentation/monthly_reports/user_agreement_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_view_model.dart'; +import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_view_model.dart'; import 'package:provider/provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -93,14 +93,14 @@ class _UserAgreementPageState extends State { margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.9), + color: AppColors.whiteColor, borderRadius: BorderRadius.circular(8), ), child: Text( _errorMessage!, textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.red, + style: TextStyle( + color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, ), ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 81c5ee4..11c6c00 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -23,7 +23,12 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_sele import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; +import '../core/dependencies.dart'; +import '../features/monthly_reports/monthly_reports_repo.dart'; +import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../presentation/monthly_reports/monthly_reports_page.dart'; +import '../services/error_handler_service.dart'; +import 'package:provider/provider.dart'; class AppRoutes { static const String initialRoute = '/initialRoute'; @@ -83,7 +88,14 @@ class AppRoutes { // healthCalculatorsPage: (context) => HealthCalculatorsPage(), - monthlyReports: (context) => MonthlyReportsPage() + // monthlyReports: (context) => MonthlyReportsPage() + monthlyReports: (context) => ChangeNotifierProvider( + create: (_) => MonthlyReportsViewModel( + monthlyReportsRepo: getIt(), + errorHandlerService: getIt(), + ), + child: const MonthlyReportsPage(), + ), }; } From 7a03242396d96e49544c26f765a86128076c8395 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 24 Dec 2025 11:26:19 +0300 Subject: [PATCH 09/46] added monthly report --- lib/core/api_consts.dart | 4 +++- lib/generated/locale_keys.g.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 778f751..390e448 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -703,7 +703,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -829,6 +829,8 @@ class ApiConsts { static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; + static final String getMonthlyReports = 'Services/Patients.svc/Rest/UpdatePateintHealthSummaryReport'; + static final String updatePatientEmail = 'Services/Patients.svc/Rest/UpdatePateintEmail'; // Ancillary Order Apis static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index d76422b..44d671f 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -476,7 +476,7 @@ abstract class LocaleKeys { static const shareReview = 'shareReview'; static const review = 'review'; static const viewMedicalFile = 'viewMedicalFile'; - static const viewAllServices = 'viewAllServices'; + static String get viewAllServices => 'viewAllServices'; static const medicalFile = 'medicalFile'; static const verified = 'verified'; static const checkup = 'checkup'; From a53602dc8cc40d962f6e4b50ed4d5627fd844a6e Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 24 Dec 2025 11:26:32 +0300 Subject: [PATCH 10/46] added monthly report --- android/app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 6c4ac3d..328e8fc 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -19,5 +19,5 @@ Geofence requests happened too frequently. - sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg + From 62bb667cb8edf7acec48982eaa021400bab47655 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 31 Dec 2025 11:38:51 +0300 Subject: [PATCH 11/46] added parking part --- lib/core/dependencies.dart | 2 +- .../hmg_services/services_page.dart | 121 ++++++++------- .../monthly_reports/monthly_reports_page.dart | 2 +- lib/presentation/parking/paking_page.dart | 118 +++++++++++++++ lib/presentation/parking/parking_slot.dart | 142 ++++++++++++++++++ lib/routes/app_routes.dart | 2 +- 6 files changed, 333 insertions(+), 54 deletions(-) create mode 100644 lib/presentation/parking/paking_page.dart create mode 100644 lib/presentation/parking/parking_slot.dart diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 2f45f37..555ce29 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -48,7 +48,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_r import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +// import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index bfd9f30..7ae7916 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -21,6 +21,7 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; + import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -31,6 +32,7 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import '../../core/dependencies.dart' show getIt; @@ -108,27 +110,27 @@ class ServicesPage extends StatelessWidget { true, route: AppRoutes.homeHealthCarePage, ), - HmgServicesComponentModel( - 12, - "Latest News".needTranslation, - "".needTranslation, - AppAssets.news, - true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: "https://twitter.com/HMG", - isExternalLink: true, - ), - HmgServicesComponentModel( - 12, - "Monthly Reports".needTranslation, - "".needTranslation, - AppAssets.report_icon, - true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: AppRoutes.monthlyReports, - ), + // HmgServicesComponentModel( + // 12, + // "Latest News".needTranslation, + // "".needTranslation, + // AppAssets.news, + // true, + // bgColor: AppColors.bgGreenColor, + // textColor: AppColors.blackColor, + // route: "https://twitter.com/HMG", + // isExternalLink: true, + // ), + // HmgServicesComponentModel( + // 12, + // "Monthly Reports".needTranslation, + // "".needTranslation, + // AppAssets.report_icon, + // true, + // bgColor: AppColors.bgGreenColor, + // textColor: AppColors.blackColor, + // route: AppRoutes.monthlyReports, + // ), ]; late final List hmgHealthToolServices = [ @@ -169,17 +171,17 @@ class ServicesPage extends StatelessWidget { route: AppRoutes.smartWatches, // route: AppRoutes.huaweiHealthExample, ), - HmgServicesComponentModel( - 12, - "Latest News".needTranslation, - "".needTranslation, - AppAssets.news, - true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: "https://twitter.com/HMG", - isExternalLink: true, - ), + // HmgServicesComponentModel( + // 12, + // "Latest News".needTranslation, + // "".needTranslation, + // AppAssets.news, + // true, + // bgColor: AppColors.bgGreenColor, + // textColor: AppColors.blackColor, + // route: "https://twitter.com/HMG", + // isExternalLink: true, + // ), HmgServicesComponentModel( 12, "Monthly Reports".needTranslation, @@ -460,25 +462,42 @@ class ServicesPage extends StatelessWidget { ), SizedBox(width: 16.w), Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.car_parking_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) - ], + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ParkingPage(), + ), + ); + }, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.car_parking_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ).onPress(() { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ParkingPage(), + ), + ); + }), ), ), ), diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart index 78c3f30..d1a4d0c 100644 --- a/lib/presentation/monthly_reports/monthly_reports_page.dart +++ b/lib/presentation/monthly_reports/monthly_reports_page.dart @@ -82,7 +82,7 @@ class _MonthlyReportsPageState extends State { setState(() => isHealthSummaryEnabled = true); _showSuccessSnackBar(); } else { - _showError("Failed to update".needTranslation); + // _showError("Failed to update".needTranslation); } } diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart new file mode 100644 index 0000000..ce9b6ab --- /dev/null +++ b/lib/presentation/parking/paking_page.dart @@ -0,0 +1,118 @@ + + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/presentation/parking/parking_slot.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; + +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/routes/custom_page_route.dart'; + +class ParkingPage extends StatefulWidget { + const ParkingPage({super.key}); + + @override + State createState() => _ParkingPageState(); +} + +class _ParkingPageState extends State { + String? scannedCode; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Parking".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 27.f, + fontWeight: FontWeight.w600)), + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + // margin: EdgeInsets.all(10), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Text( + "Dr. Sulaiman Al Habib hospital are conduction a test for the emerging corona" + " virus and issuing travel certificates 24/7 in a short time and with high accuracy." + " Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches " + "to conduct a corona test within few minutes. Dr. Sulaiman Al Habib hospital are conduction" + " a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. " + "Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes.", + style: TextStyle( + color: AppColors.textColor, + fontSize: 12, height: 1.4, fontWeight: FontWeight.w500), + ), + ), + ).paddingOnly( top: 16, bottom: 16), + + ], + ), + ), + ), + + /// Bottom button + Container + ( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(24.h), + child: SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primaryRedColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: ParkingSlot(), + ) ); + }, + child: Text( + "Read Barcodes", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart new file mode 100644 index 0000000..094dcb7 --- /dev/null +++ b/lib/presentation/parking/parking_slot.dart @@ -0,0 +1,142 @@ + + + + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; + +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/chip/app_custom_chip_widget.dart'; + +class ParkingSlot extends StatefulWidget { + const ParkingSlot({super.key}); + + @override + State createState() => _ParkingSlotState(); +} + +class _ParkingSlotState extends State { + String? scannedCode; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: LayoutBuilder( + builder: (context, constraints) { + final maxW = constraints.maxWidth; + final contentW = maxW > 600 ? 600.0 : maxW; // حد أقصى للتابلت + + return Align( + alignment: Alignment.topCenter, + child: SizedBox( + width: contentW, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Parking Slot Details", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor, + ), + ), + SizedBox(height: 16.h), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + AppCustomChipWidget(labelText: "Slot: B-24".needTranslation), + AppCustomChipWidget(labelText: "Basement: Zone B".needTranslation), + AppCustomChipWidget(labelText: "Date: 16 Dec 2025".needTranslation), + AppCustomChipWidget(labelText: "Parked Since: 10:32 AM".needTranslation), + ], + ), + ], + ), + ), + ), + + SizedBox(height: 24.h), + + SizedBox( + width: double.infinity, + height: 48.h, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primaryRedColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: () {}, + child: Text( + "Get Direction", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + + const Spacer(), + SizedBox( + width: double.infinity, + height: 48.h, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + side: BorderSide(color: AppColors.primaryRedColor), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: () { + // Reset direction logic + }, + child: Text( + "Reset Direction", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.primaryRedColor, + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + + ); + } +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 970d2b9..db38e78 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -89,7 +89,7 @@ class AppRoutes { huaweiHealthExample: (context) => HuaweiHealthExample(), // - healthCalculatorsPage: (context) => HealthCalculatorsPage(), + // healthCalculatorsPage: (context) => HealthCalculatorsPage(), // monthlyReports: (context) => MonthlyReportsPage() monthlyReports: (context) => ChangeNotifierProvider( create: (_) => MonthlyReportsViewModel( From 597579e6778d98de2b1a2edc11493e94868d3bc8 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Thu, 1 Jan 2026 15:17:27 +0300 Subject: [PATCH 12/46] refactoring --- .../water_monitor_view_model.dart | 9 +++--- .../widgets/water_action_buttons_widget.dart | 32 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index 01167fe..18ffddd 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -1065,9 +1065,8 @@ class WaterMonitorViewModel extends ChangeNotifier { notifyListeners(); return false; }, - (apiModel) { + (apiModel) async { log("Insert user activity success: ${apiModel.data.toString()}"); - // Update consumed amount and goal from the response if (apiModel.data != null && apiModel.data is List && (apiModel.data as List).isNotEmpty) { final progressData = (apiModel.data as List).first; if (progressData is Map) { @@ -1090,7 +1089,7 @@ class WaterMonitorViewModel extends ChangeNotifier { } // Refresh progress data to ensure consistency - fetchUserProgressForMonitoring(); + await fetchUserProgressForMonitoring(); } _isLoading = false; @@ -1140,7 +1139,7 @@ class WaterMonitorViewModel extends ChangeNotifier { notifyListeners(); return false; }, - (apiModel) { + (apiModel) async { log("Undo user activity success: ${apiModel.data.toString()}"); // Update consumed amount and goal from the response @@ -1164,8 +1163,8 @@ class WaterMonitorViewModel extends ChangeNotifier { } } } + await fetchUserProgressForMonitoring(); } - fetchUserProgressForMonitoring(); _isLoading = false; notifyListeners(); return true; diff --git a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart index 6546f77..9779562 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -18,23 +18,29 @@ class WaterActionButtonsWidget extends StatelessWidget { return Consumer(builder: (context, vm, _) { final cupAmount = vm.selectedCupCapacityMl; final isGoalAchieved = vm.progressPercent >= 100 || vm.nextDrinkTime.toLowerCase().contains('goal achieved'); + final isDisabled = vm.isLoading || isGoalAchieved; return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - InkWell( - onTap: () async { - if (cupAmount > 0) { - await vm.undoUserActivity(); - } - }, - child: Utils.buildSvgWithAssets( - icon: AppAssets.minimizeIcon, - height: 20.h, - width: 20.h, - iconColor: AppColors.textColor, + Opacity( + opacity: vm.isLoading ? 0.4 : 1.0, + child: InkWell( + onTap: vm.isLoading + ? null + : () async { + if (cupAmount > 0) { + await vm.undoUserActivity(); + } + }, + child: Utils.buildSvgWithAssets( + icon: AppAssets.minimizeIcon, + height: 20.h, + width: 20.h, + iconColor: AppColors.textColor, + ), ), ), Container( @@ -51,9 +57,9 @@ class WaterActionButtonsWidget extends StatelessWidget { ), ), Opacity( - opacity: isGoalAchieved ? 0.4 : 1.0, + opacity: isDisabled ? 0.4 : 1.0, child: InkWell( - onTap: isGoalAchieved + onTap: isDisabled ? null : () async { if (cupAmount > 0) { From 49d091547a6d6e366b6a11406293610b8f01dfee Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 4 Jan 2026 22:30:43 +0300 Subject: [PATCH 13/46] Till Triage Completed --- contexts/AuthContext.tsx | 31 ++ lib/core/api_consts.dart | 6 +- lib/core/utils/utils.dart | 22 -- .../symptoms_user_details_response_model.dart | 188 +++++++++ .../resp_models/triage_response_model.dart | 209 ++++++++++ .../symptoms_checker_repo.dart | 213 +++++++++-- .../symptoms_checker_view_model.dart | 197 ++++++++++ .../organ_selector_screen.dart | 29 +- .../possible_conditions_screen.dart | 14 +- .../symptoms_checker/risk_factors_screen.dart | 12 +- .../symptoms_checker/suggestions_screen.dart | 12 +- .../symptoms_selector_screen.dart | 12 +- .../symptoms_checker/triage_screen.dart | 359 ++++++++++++++---- .../pages/age_selection_page.dart | 3 - .../water_monitor_settings_screen.dart | 37 +- lib/services/dialog_service.dart | 18 +- services/api.ts | 1 + types/user.ts | 57 +++ utils/apiHelpers.ts | 18 + 19 files changed, 1252 insertions(+), 186 deletions(-) create mode 100644 contexts/AuthContext.tsx create mode 100644 lib/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart create mode 100644 lib/features/symptoms_checker/models/resp_models/triage_response_model.dart create mode 100644 services/api.ts create mode 100644 types/user.ts create mode 100644 utils/apiHelpers.ts diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx new file mode 100644 index 0000000..48571a8 --- /dev/null +++ b/contexts/AuthContext.tsx @@ -0,0 +1,31 @@ +// ...existing imports... +import { apiService } from '../services/api'; + +// ...existing code... + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + // ...existing state... + + const login = async (nationalId: string, password: string) => { + try { + setIsLoading(true); + + // Call the real API + const user = await apiService.getUserDetails(nationalId, password); + + // Store user data + await AsyncStorage.setItem('user', JSON.stringify(user)); + await AsyncStorage.setItem('authToken', user.authToken); + + setUser(user); + } catch (error) { + console.error('Login error:', error); + throw new Error('Invalid credentials or network error'); + } finally { + setIsLoading(false); + } + }; + + // ...existing code... +}; + diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 0febdbe..65ac1ef 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -825,11 +825,13 @@ class ApiConsts { static final String updateHHCOrder = 'api/hhc/update'; static final String addHHCOrder = 'api/HHC/add'; - // SYMPTOMS CHECKER + // SYMPTOMS CHECKER API + + static final String symptomsUserLogin = '$symptomsCheckerApi/user_login'; static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName'; static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors'; static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion'; - static final String diagnosis = '$symptomsCheckerApi/diagnosis'; + static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; static final String explain = '$symptomsCheckerApi/explain'; //E-REFERRAL SERVICES diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 0302818..a30763f 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -218,16 +218,6 @@ class Utils { return await prefs.remove(key); } - static void showLoading({bool isNeedBinding = true}) { - if (isNeedBinding) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showLoadingDialog(); - }); - } else { - showLoadingDialog(); - } - } - static void showLoadingDialog() { _isLoadingVisible = true; showDialog( @@ -244,18 +234,6 @@ class Utils { ); } - static void hideLoading() { - try { - if (_isLoadingVisible) { - _isLoadingVisible = false; - Navigator.of(navigationService.navigatorKey.currentContext!).pop(); - } - _isLoadingVisible = false; - } catch (e) { - log("errr: ${e.toString()}"); - } - } - static List uniqueBy(List list, K Function(T) keySelector) { final seenKeys = {}; return list.where((item) => seenKeys.add(keySelector(item))).toList(); diff --git a/lib/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart b/lib/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart new file mode 100644 index 0000000..c0466c4 --- /dev/null +++ b/lib/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart @@ -0,0 +1,188 @@ +class SymptomsUserDetailsResponseModel { + final TokenDetails? tokenDetails; + final UserDetails? userDetails; + final String? sessionId; + + SymptomsUserDetailsResponseModel({ + this.tokenDetails, + this.userDetails, + this.sessionId, + }); + + factory SymptomsUserDetailsResponseModel.fromJson(Map json) { + return SymptomsUserDetailsResponseModel( + tokenDetails: json['tokenDetails'] != null ? TokenDetails.fromJson(json['tokenDetails']) : null, + userDetails: json['userDetails'] != null ? UserDetails.fromJson(json['userDetails']) : null, + sessionId: json['sessionId'], + ); + } + + Map toJson() { + return { + 'tokenDetails': tokenDetails?.toJson(), + 'userDetails': userDetails?.toJson(), + 'sessionId': sessionId, + }; + } +} + +class TokenDetails { + final String? id; + final String? authToken; + final int? expiresIn; + + TokenDetails({ + this.id, + this.authToken, + this.expiresIn, + }); + + factory TokenDetails.fromJson(Map json) { + return TokenDetails( + id: json['id'], + authToken: json['auth_token'], + expiresIn: json['expires_in'], + ); + } + + Map toJson() { + return { + 'id': id, + 'auth_token': authToken, + 'expires_in': expiresIn, + }; + } +} + +class UserDetails { + final String? fileNo; + final String? nationalId; + final String? email; + final String? dateOfBirth; + final String? dateOfBirthHijri; + final int? age; + final UserName? name; + final int? maritalStatus; + final String? maritalStatusCode; + final String? nationality; + final String? nationalityIsoCode; + final String? occupation; + final int? idType; + final int? gender; + final String? jwtToken; + final String? countryDialCode; + final String? phoneNo; + + UserDetails({ + this.fileNo, + this.nationalId, + this.email, + this.dateOfBirth, + this.dateOfBirthHijri, + this.age, + this.name, + this.maritalStatus, + this.maritalStatusCode, + this.nationality, + this.nationalityIsoCode, + this.occupation, + this.idType, + this.gender, + this.jwtToken, + this.countryDialCode, + this.phoneNo, + }); + + factory UserDetails.fromJson(Map json) { + return UserDetails( + fileNo: json['FileNo'], + nationalId: json['national_id'], + email: json['email'], + dateOfBirth: json['date_of_birth'], + dateOfBirthHijri: json['date_of_birth_hijri'], + age: json['age'], + name: json['name'] != null ? UserName.fromJson(json['name']) : null, + maritalStatus: json['marital_status'], + maritalStatusCode: json['marital_status_code'], + nationality: json['nationality'], + nationalityIsoCode: json['nationality_iso_code'], + occupation: json['occupation'], + idType: json['id_type'], + gender: json['gender'], + jwtToken: json['jwt_token'], + countryDialCode: json['country_dial_code'], + phoneNo: json['phone_no'], + ); + } + + Map toJson() { + return { + 'FileNo': fileNo, + 'national_id': nationalId, + 'email': email, + 'date_of_birth': dateOfBirth, + 'date_of_birth_hijri': dateOfBirthHijri, + 'age': age, + 'name': name?.toJson(), + 'marital_status': maritalStatus, + 'marital_status_code': maritalStatusCode, + 'nationality': nationality, + 'nationality_iso_code': nationalityIsoCode, + 'occupation': occupation, + 'id_type': idType, + 'gender': gender, + 'jwt_token': jwtToken, + 'country_dial_code': countryDialCode, + 'phone_no': phoneNo, + }; + } + + // Helper method to get full name + String getFullName(bool isArabic) { + if (name == null) return ''; + if (isArabic) { + return '${name!.firstNameAr ?? ''} ${name!.middleNameAr ?? ''} ${name!.lastNameAr ?? ''}'.trim(); + } + return '${name!.firstName ?? ''} ${name!.middleName ?? ''} ${name!.lastName ?? ''}'.trim(); + } +} + +class UserName { + final String? firstName; + final String? middleName; + final String? lastName; + final String? firstNameAr; + final String? middleNameAr; + final String? lastNameAr; + + UserName({ + this.firstName, + this.middleName, + this.lastName, + this.firstNameAr, + this.middleNameAr, + this.lastNameAr, + }); + + factory UserName.fromJson(Map json) { + return UserName( + firstName: json['first_name'], + middleName: json['middle_name'], + lastName: json['last_name'], + firstNameAr: json['first_name_ar'], + middleNameAr: json['middle_name_ar'], + lastNameAr: json['last_name_ar'], + ); + } + + Map toJson() { + return { + 'first_name': firstName, + 'middle_name': middleName, + 'last_name': lastName, + 'first_name_ar': firstNameAr, + 'middle_name_ar': middleNameAr, + 'last_name_ar': lastNameAr, + }; + } +} diff --git a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart new file mode 100644 index 0000000..f0d7e90 --- /dev/null +++ b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart @@ -0,0 +1,209 @@ +class TriageDataDetails { + final TriageQuestion? question; + final List? conditions; + final bool? hasEmergencyEvidence; + final bool? shouldStop; + final String? interviewToken; + final String? message; + final List? errorList; + final int? id; + final String? language; + final String? generalId; + final String? createDate; + final String? lastEditDate; + final String? createdBy; + final String? lastEditBy; + final bool? active; + final int? sortOrder; + final int? userType; + final String? userId; + + TriageDataDetails({ + this.question, + this.conditions, + this.hasEmergencyEvidence, + this.shouldStop, + this.interviewToken, + this.message, + this.errorList, + this.id, + this.language, + this.generalId, + this.createDate, + this.lastEditDate, + this.createdBy, + this.lastEditBy, + this.active, + this.sortOrder, + this.userType, + this.userId, + }); + + factory TriageDataDetails.fromJson(Map json) { + return TriageDataDetails( + question: json['question'] != null ? TriageQuestion.fromJson(json['question']) : null, + conditions: json['conditions'] != null ? (json['conditions'] as List).map((item) => TriageCondition.fromJson(item)).toList() : null, + hasEmergencyEvidence: json['has_emergency_evidence'], + shouldStop: json['should_stop'], + interviewToken: json['interview_token'], + message: json['Message'], + errorList: json['ErrorList'] != null ? List.from(json['ErrorList']) : null, + id: json['Id'], + language: json['language'], + generalId: json['generalId'], + createDate: json['CreateDate'], + lastEditDate: json['LastEditDate'], + createdBy: json['CreatedBy'], + lastEditBy: json['LastEditBy'], + active: json['Active'], + sortOrder: json['SortOrder'], + userType: json['userType'], + userId: json['userId'], + ); + } + + Map toJson() { + return { + 'question': question?.toJson(), + 'conditions': conditions?.map((item) => item.toJson()).toList(), + 'has_emergency_evidence': hasEmergencyEvidence, + 'should_stop': shouldStop, + 'interview_token': interviewToken, + 'Message': message, + 'ErrorList': errorList, + 'Id': id, + 'language': language, + 'generalId': generalId, + 'CreateDate': createDate, + 'LastEditDate': lastEditDate, + 'CreatedBy': createdBy, + 'LastEditBy': lastEditBy, + 'Active': active, + 'SortOrder': sortOrder, + 'userType': userType, + 'userId': userId, + }; + } +} + +class TriageQuestion { + final int? type; + final String? text; + final List? items; + + TriageQuestion({ + this.type, + this.text, + this.items, + }); + + factory TriageQuestion.fromJson(Map json) { + return TriageQuestion( + type: json['type'], + text: json['text'], + items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null, + ); + } + + Map toJson() { + return { + 'type': type, + 'text': text, + 'items': items?.map((item) => item.toJson()).toList(), + }; + } +} + +class TriageQuestionItem { + final String? id; + final String? name; + final List? choices; + + TriageQuestionItem({ + this.id, + this.name, + this.choices, + }); + + factory TriageQuestionItem.fromJson(Map json) { + return TriageQuestionItem( + id: json['id'], + name: json['name'], + choices: json['choices'] != null ? (json['choices'] as List).map((item) => TriageChoice.fromJson(item)).toList() : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'choices': choices?.map((item) => item.toJson()).toList(), + }; + } +} + +class TriageChoice { + final String? id; + final String? label; + + TriageChoice({ + this.id, + this.label, + }); + + factory TriageChoice.fromJson(Map json) { + return TriageChoice( + id: json['id'], + label: json['label'], + ); + } + + Map toJson() { + return { + 'id': id, + 'label': label, + }; + } +} + +class TriageCondition { + final String? id; + final String? name; + final String? commonName; + final double? probability; + final dynamic conditionDetails; + + TriageCondition({ + this.id, + this.name, + this.commonName, + this.probability, + this.conditionDetails, + }); + + factory TriageCondition.fromJson(Map json) { + return TriageCondition( + id: json['id'], + name: json['name'], + commonName: json['common_name'], + probability: json['probability']?.toDouble(), + conditionDetails: json['condition_details'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'common_name': commonName, + 'probability': probability, + 'condition_details': conditionDetails, + }; + } + + /// Get probability as percentage + String getProbabilityPercentage() { + if (probability == null) return '0%'; + return '${(probability! * 100).toStringAsFixed(1)}%'; + } +} diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 954d414..2d9a99b 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; @@ -8,11 +7,20 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class SymptomsCheckerRepo { + Future>> getUserDetails({ + required String userName, + required String password, + }); + Future>> getBodySymptomsByName({ required List organNames, + required String userSessionToken, + required int gender, }); Future>> getRiskFactors({ @@ -20,6 +28,9 @@ abstract class SymptomsCheckerRepo { required String sex, required List evidenceIds, required String language, + required String userSessionToken, + required int gender, + required String sessionId, }); Future>> getSuggestions({ @@ -27,6 +38,19 @@ abstract class SymptomsCheckerRepo { required String sex, required List evidenceIds, required String language, + required String userSessionToken, + required String sessionId, + required int gender, + }); + + Future>> getDiagnosisForTriage({ + required int age, + required String sex, + required List evidenceIds, + required String language, + required String userSessionToken, + required int gender, + required String sessionId, }); } @@ -37,11 +61,71 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService}); @override - Future>> getBodySymptomsByName({required List organNames}) async { - log("GetBodySymptomsByName Request URL: ${ApiConsts.getBodySymptomsByName}"); - log("GetBodySymptomsByName Request Body: ${jsonEncode(organNames)}"); + Future>> getUserDetails({ + required String userName, + required String password, + }) async { + Map body = {"userName": userName, "password": password}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.symptomsUserLogin, + body: body, + isExternal: true, + isAllowAny: true, + isBodyPlainText: false, + onFailure: (error, statusCode, {messageStatus, failureType}) { + loggerService.logError("getUserDetails API Failed: $error"); + failure = failureType ?? ServerFailure(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // Parse response if it's a string + final Map responseData = response is String ? jsonDecode(response) : response; - Map headers = {'Content-Type': 'application/json', 'Accept': 'text/plain'}; + SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = SymptomsUserDetailsResponseModel.fromJson(responseData); + + apiResponse = GenericApiModel( + messageStatus: messageStatus ?? 1, + statusCode: statusCode, + errorMessage: errorMessage, + data: symptomsUserDetailsResponseModel, + ); + } catch (e, stackTrace) { + loggerService.logError("Error parsing getUserDetails response: $e"); + loggerService.logError("StackTrace: $stackTrace"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e, stackTrace) { + loggerService.logError("Exception in getUserDetails: $e"); + loggerService.logError("StackTrace: $stackTrace"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getBodySymptomsByName({ + required List organNames, + required String userSessionToken, + required int gender, + }) async { + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', + }; + Map body = { + 'bodyPartName': organNames, + 'gender': gender, + }; try { GenericApiModel? apiResponse; @@ -50,21 +134,15 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { await apiClient.post( ApiConsts.getBodySymptomsByName, apiHeaders: headers, - body: jsonEncode(organNames), + body: body, isExternal: true, isAllowAny: true, - isBodyPlainText: true, + isBodyPlainText: false, onFailure: (error, statusCode, {messageStatus, failureType}) { - loggerService.logError("GetBodySymptomsByName API Failed: $error"); - log("GetBodySymptomsByName Failed: $error, Status: $statusCode"); failure = failureType ?? ServerFailure(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - log("GetBodySymptomsByName Response Status: $statusCode"); - loggerService.logInfo("GetBodySymptomsByName API Success: $response"); - log("GetBodySymptomsByName Response: $response"); - BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response); apiResponse = GenericApiModel( @@ -76,7 +154,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } catch (e, stackTrace) { loggerService.logError("Error parsing GetBodySymptomsByName response: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Parse Error: $e"); failure = DataParsingFailure(e.toString()); } }, @@ -88,7 +165,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } catch (e, stackTrace) { loggerService.logError("Exception in getBodySymptomsByName: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Exception: $e"); return Left(UnknownFailure(e.toString())); } } @@ -99,6 +175,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { required String sex, required List evidenceIds, required String language, + required String userSessionToken, + required int gender, + required String sessionId, }) async { final Map body = { "age": { @@ -107,6 +186,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { "sex": sex, "evidence": evidenceIds.map((id) => {"id": id}).toList(), "language": language, + "generalId": sessionId, + }; + + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', }; try { @@ -115,17 +200,16 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { await apiClient.post( ApiConsts.getRiskFactors, + apiHeaders: headers, body: body, isExternal: true, isAllowAny: true, onFailure: (error, statusCode, {messageStatus, failureType}) { - log("GetRiskFactors Failed: $error, Status: $statusCode"); + loggerService.logError("GetRiskFactors API Failed: $error"); failure = failureType ?? ServerFailure(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - log("GetRiskFactors Response: $response"); - // Parse response if it's a string final Map responseData = response is String ? jsonDecode(response) : response; @@ -140,7 +224,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } catch (e, stackTrace) { loggerService.logError("Error parsing GetRiskFactors response: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Parse Error: $e"); failure = DataParsingFailure(e.toString()); } }, @@ -152,7 +235,79 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } catch (e, stackTrace) { loggerService.logError("Exception in getRiskFactors: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Exception: $e"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getDiagnosisForTriage({ + required int age, + required String sex, + required List evidenceIds, + required String language, + required String userSessionToken, + required int gender, + required String sessionId, + }) async { + final Map body = { + "age": { + "value": age, + }, + "sex": sex, + "evidence": evidenceIds.map((id) => {"id": id}).toList(), + "language": language, + "suggest_method": "diagnosis", + "generalId": sessionId, + }; + + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.diagnosis, + apiHeaders: headers, + body: body, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + loggerService.logError("getDiagnosisForTriage API Failed: $error"); + failure = failureType ?? ServerFailure(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // Parse response if it's a string + final Map responseData = response is String ? jsonDecode(response) : response; + + final updatedResponseData = responseData['dataDetails']; + + TriageDataDetails riskFactorsResponse = TriageDataDetails.fromJson(updatedResponseData); + + apiResponse = GenericApiModel( + messageStatus: messageStatus ?? 1, + statusCode: statusCode, + errorMessage: errorMessage, + data: riskFactorsResponse, + ); + } catch (e, stackTrace) { + loggerService.logError("Error parsing getDiagnosisForTriage response: $e"); + loggerService.logError("StackTrace: $stackTrace"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e, stackTrace) { + loggerService.logError("Exception in getDiagnosisForTriage: $e"); + loggerService.logError("StackTrace: $stackTrace"); return Left(UnknownFailure(e.toString())); } } @@ -163,6 +318,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { required String sex, required List evidenceIds, required String language, + required String userSessionToken, + required String sessionId, + required int gender, }) async { final Map body = { "age": { @@ -171,6 +329,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { "sex": sex, "evidence": evidenceIds.map((id) => {"id": id}).toList(), "language": language, + "generalId": sessionId, + }; + + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', }; try { @@ -179,17 +343,16 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { await apiClient.post( ApiConsts.getSuggestions, + apiHeaders: headers, body: body, isExternal: true, isAllowAny: true, onFailure: (error, statusCode, {messageStatus, failureType}) { - log("getSuggestions Failed: $error, Status: $statusCode"); + loggerService.logError("GetSuggestions API Failed: $error"); failure = failureType ?? ServerFailure(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - log("getSuggestions Response: $response"); - // Parse response if it's a string final Map responseData = response is String ? jsonDecode(response) : response; @@ -202,9 +365,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { data: riskFactorsResponse, ); } catch (e, stackTrace) { - loggerService.logError("Error parsing getSuggestions response: $e"); + loggerService.logError("Error parsing GetSuggestions response: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Parse Error: $e"); failure = DataParsingFailure(e.toString()); } }, @@ -216,7 +378,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } catch (e, stackTrace) { loggerService.logError("Exception in getSuggestions: $e"); loggerService.logError("StackTrace: $stackTrace"); - log("Exception: $e"); return Left(UnknownFailure(e.toString())); } } diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index da439c6..b41bab2 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -35,11 +37,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { bool isBodySymptomsLoading = false; bool isRiskFactorsLoading = false; bool isSuggestionsLoading = false; + bool isTriageDiagnosisLoading = false; // API data storage - using API models directly + SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel; BodySymptomResponseModel? bodySymptomResponse; RiskAndSuggestionsResponseModel? riskFactorsResponse; RiskAndSuggestionsResponseModel? suggestionsResponse; + TriageDataDetails? triageDataDetails; + + // Triage state + int? _selectedTriageChoiceIndex; // Selected risk factors tracking final Set _selectedRiskFactorIds = {}; @@ -92,6 +100,23 @@ class SymptomsCheckerViewModel extends ChangeNotifier { String? get tooltipOrganId => _tooltipOrganId; + String get currentSessionAuthToken => symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? ""; + + String get currentSessionId => symptomsUserDetailsResponseModel?.sessionId ?? ""; + + // Triage-related getters + bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false; + + bool get hasEmergencyEvidence => triageDataDetails?.hasEmergencyEvidence ?? false; + + String? get currentInterviewToken => triageDataDetails?.interviewToken; + + TriageQuestion? get currentTriageQuestion => triageDataDetails?.question; + + List? get currentConditions => triageDataDetails?.conditions; + + int? get selectedTriageChoiceIndex => _selectedTriageChoiceIndex; + /// Get organs for current view List get currentOrgans => OrganData.getOrgansForView(_currentView); @@ -391,6 +416,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { age: _selectedAge!, sex: _selectedGender!.toLowerCase(), evidenceIds: evidenceIds, + sessionId: currentSessionId, language: appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { if (onSuccess != null) { @@ -409,6 +435,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { Future getRiskFactors({ required int age, required String sex, + required String sessionId, required List evidenceIds, required String language, Function(RiskAndSuggestionsResponseModel)? onSuccess, @@ -422,6 +449,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { sex: sex, evidenceIds: evidenceIds, language: language, + sessionId: sessionId, + userSessionToken: currentSessionAuthToken, + gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( @@ -502,6 +532,25 @@ class SymptomsCheckerViewModel extends ChangeNotifier { notifyListeners(); } + /// Get all evidence IDs (symptoms + risk factors + suggestions) for triage/diagnosis + List getAllEvidenceIds() { + List evidenceIds = []; + + // Add selected symptoms + final selectedSymptoms = getAllSelectedSymptoms(); + evidenceIds.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!)); + + // Add selected risk factors (excluding "not_applicable") + final selectedRiskFactors = getAllSelectedRiskFactors(); + evidenceIds.addAll(selectedRiskFactors.where((rf) => rf.id != null && rf.id != "not_applicable").map((rf) => rf.id!)); + + // Add selected suggestions (excluding "not_applicable") + final selectedSuggestions = getAllSelectedSuggestions(); + evidenceIds.addAll(selectedSuggestions.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!)); + + return evidenceIds; + } + /// Fetch risk factors based on selected symptoms Future fetchSuggestions({ Function()? onSuccess, @@ -571,6 +620,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { sex: sex, evidenceIds: evidenceIds, language: language, + sessionId: currentSessionId, + userSessionToken: currentSessionAuthToken, + gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( @@ -612,6 +664,109 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } + /// Call Diagnosis API for Triage - This is called iteratively until shouldStop is true + Future getDiagnosisForTriage({ + required int age, + required String sex, + required List evidenceIds, + required String language, + Function(TriageDataDetails)? onSuccess, + Function(String)? onError, + }) async { + isTriageDiagnosisLoading = true; + notifyListeners(); + + final result = await symptomsCheckerRepo.getDiagnosisForTriage( + age: age, + sex: sex, + evidenceIds: evidenceIds, + language: language, + sessionId: currentSessionId, + userSessionToken: currentSessionAuthToken, + gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, + ); + + result.fold( + (failure) async { + isTriageDiagnosisLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isTriageDiagnosisLoading = false; + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + triageDataDetails = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data!); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Failed to fetch diagnosis'); + } + } + }, + ); + } + + /// Convenience method to start or continue the triage process + /// This automatically uses all selected evidence (symptoms + risk factors + suggestions) + Future startOrContinueTriage({ + Function()? onSuccess, + Function(String)? onError, + }) async { + // Validate user info + if (_selectedAge == null || _selectedGender == null) { + if (onError != null) { + onError('User information is incomplete'); + } + return; + } + + // Get all evidence IDs + final evidenceIds = getAllEvidenceIds(); + + if (evidenceIds.isEmpty) { + if (onError != null) { + onError('No evidence selected'); + } + return; + } + + await getDiagnosisForTriage( + age: _selectedAge!, + sex: _selectedGender!.toLowerCase(), + evidenceIds: evidenceIds, + language: appState.isArabic() ? 'ar' : 'en', + onSuccess: (response) { + if (onSuccess != null) { + onSuccess(); + } + }, + onError: (error) { + if (onError != null) { + onError(error); + } + }, + ); + } + + /// Select a triage choice option + void selectTriageChoice(int choiceIndex) { + _selectedTriageChoiceIndex = choiceIndex; + notifyListeners(); + } + + /// Reset triage choice selection + void resetTriageChoice() { + _selectedTriageChoiceIndex = null; + notifyListeners(); + } + void reset() { _currentView = BodyView.front; _selectedOrganIds.clear(); @@ -621,6 +776,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { bodySymptomResponse = null; riskFactorsResponse = null; suggestionsResponse = null; + triageDataDetails = null; + isTriageDiagnosisLoading = false; + _selectedTriageChoiceIndex = null; _isBottomSheetExpanded = false; _tooltipTimer?.cancel(); _tooltipOrganId = null; @@ -725,6 +883,43 @@ class SymptomsCheckerViewModel extends ChangeNotifier { }; } + Future getSymptomsUserDetails({ + required String userName, + required String password, + Function()? onSuccess, + Function(String)? onError, + }) async { + isBodySymptomsLoading = true; + notifyListeners(); + final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password); + + result.fold( + (failure) async { + isBodySymptomsLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isBodySymptomsLoading = false; + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + symptomsUserDetailsResponseModel = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Failed to fetch symptoms'); + } + } + }, + ); + } + Future getBodySymptomsByName({ required List organNames, Function(BodySymptomResponseModel)? onSuccess, @@ -735,6 +930,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getBodySymptomsByName( organNames: organNames, + userSessionToken: currentSessionAuthToken, + gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, ); result.fold( diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index c23b956..4e0ec10 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -10,9 +10,11 @@ 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/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class OrganSelectorPage extends StatefulWidget { @@ -24,25 +26,38 @@ class OrganSelectorPage extends StatefulWidget { class _OrganSelectorPageState extends State { late final AppState _appState; + late final DialogService dialogService; @override void initState() { super.initState(); _appState = getIt.get(); + dialogService = getIt(); } - void _onNextPressed(SymptomsCheckerViewModel viewModel) { + void _onNextPressed(SymptomsCheckerViewModel viewModel) async { if (!viewModel.validateSelection()) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Please select at least one organ'.needTranslation), - backgroundColor: AppColors.errorColor, - ), + dialogService.showErrorBottomSheet( + message: 'Please select at least one organ'.needTranslation, ); return; } + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + + final String userName = 'guest_user'; + final String password = '123456'; - context.navigateWithName(AppRoutes.symptomsSelectorScreen); + await viewModel.getSymptomsUserDetails( + userName: userName, + password: password, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + context.navigateWithName(AppRoutes.symptomsSelectorScreen); + }, + onError: (String error) { + LoaderBottomSheet.hideLoader(); + }, + ); } @override diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 2c99515..188d07b 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -44,7 +45,7 @@ class PossibleConditionsScreen extends StatelessWidget { ); } - Widget _buildPredictionsList(List conditions) { + Widget _buildPredictionsList(BuildContext context, List conditions) { if (conditions.isEmpty) { return Center( child: Padding( @@ -60,6 +61,8 @@ class PossibleConditionsScreen extends StatelessWidget { ); } + final dialogService = getIt(); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -82,11 +85,8 @@ class PossibleConditionsScreen extends StatelessWidget { description: conditionModel.description, possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum, onActionPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('We are not available for a week. May you Rest In Peace :('), - backgroundColor: AppColors.primaryRedColor, - ), + dialogService.showErrorBottomSheet( + message: 'We are not available for a week. May you Rest In Peace :(', ); }, ); @@ -168,7 +168,7 @@ class PossibleConditionsScreen extends StatelessWidget { if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) { return _buildLoadingShimmer(); } - return _buildPredictionsList(dummyConditions); + return _buildPredictionsList(context, dummyConditions); }, ), ), diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index d4ff2cb..aef7ce7 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -2,11 +2,13 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/symptoms_checker/symptoms_checker_view_model.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'; @@ -20,9 +22,12 @@ class RiskFactorsScreen extends StatefulWidget { } class _RiskFactorsScreenState extends State { + late DialogService dialogService; + @override void initState() { super.initState(); + dialogService = getIt(); // Fetch risk factors based on selected symptoms WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -38,11 +43,8 @@ class _RiskFactorsScreenState extends State { if (viewModel.hasSelectedRiskFactors) { context.navigateWithName(AppRoutes.suggestionsScreen); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Please select at least one risk before proceeding'.needTranslation), - backgroundColor: AppColors.errorColor, - ), + dialogService.showErrorBottomSheet( + message: 'Please select at least one risk before proceeding'.needTranslation, ); } } diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index f2aa71e..b5b4388 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/symptoms_checker/symptoms_checker_view_model.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'; @@ -19,9 +21,12 @@ class SuggestionsScreen extends StatefulWidget { } class _SuggestionsScreenState extends State { + late DialogService dialogService; + @override void initState() { super.initState(); + dialogService = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -40,11 +45,8 @@ class _SuggestionsScreenState extends State { // Navigate to triage screen context.navigateWithName(AppRoutes.triageScreen); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Please select at least one option before proceeding'.needTranslation), - backgroundColor: AppColors.errorColor, - ), + dialogService.showErrorBottomSheet( + message: 'Please select at least one option before proceeding'.needTranslation, ); } } diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index 522c5f8..3950974 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; @@ -23,9 +25,12 @@ class SymptomsSelectorScreen extends StatefulWidget { } class _SymptomsSelectorScreenState extends State { + late DialogService dialogService; + @override void initState() { super.initState(); + dialogService = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -38,11 +43,8 @@ class _SymptomsSelectorScreenState extends State { // Navigate to triage screen context.navigateWithName(AppRoutes.riskFactorsScreen); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Please select at least one symptom before proceeding'.needTranslation), - backgroundColor: AppColors.errorColor, - ), + dialogService.showErrorBottomSheet( + message: 'Please select at least one symptom before proceeding'.needTranslation, ); } } diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index ff0482e..8262c67 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -1,18 +1,23 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/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/symptoms_checker/data/triage_questions_data.dart'; -import 'package:hmg_patient_app_new/features/symptoms_checker/models/triage_question_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; class TriageScreen extends StatefulWidget { const TriageScreen({super.key}); @@ -22,114 +27,293 @@ class TriageScreen extends StatefulWidget { } class _TriageScreenState extends State { - late List triageQuestions; - int currentQuestionIndex = 0; + List answeredEvidenceIds = []; // Track user's answers + late SymptomsCheckerViewModel viewModel; + late DialogService dialogService; @override void initState() { super.initState(); - triageQuestions = TriageQuestionsData.getSampleTriageQuestions(); - } - - TriageQuestionModel get currentQuestion => triageQuestions[currentQuestionIndex]; - - bool get isFirstQuestion => currentQuestionIndex == 0; + viewModel = context.read(); + dialogService = getIt.get(); - bool get isLastQuestion => currentQuestionIndex == triageQuestions.length - 1; - - void _onOptionSelected(int optionIndex) { - setState(() { - currentQuestion.selectOption(optionIndex); + // Start triage process when screen loads + WidgetsBinding.instance.addPostFrameCallback((_) { + _startTriage(); }); } - void _onPreviousPressed() { - if (!isFirstQuestion) { - setState(() { - currentQuestionIndex--; - }); - } + void _startTriage() { + viewModel.startOrContinueTriage( + onSuccess: () { + _handleTriageResponse(); + }, + onError: (error) { + dialogService.showErrorBottomSheet( + message: error, + onOkPressed: () => context.pop(), + ); + }, + ); } - void _onNextPressed() { - if (currentQuestion.isAnswered) { - currentQuestion.confirmSelection(); - if (isLastQuestion) { - context.navigateWithName(AppRoutes.possibleConditionsScreen); - } else { - setState(() { - currentQuestionIndex++; - }); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Please select an option before proceeding'.needTranslation), - backgroundColor: AppColors.errorColor, - ), - ); + void _handleTriageResponse() { + if (viewModel.hasEmergencyEvidence) { + _showEmergencyDialog(); + return; + } + + if (viewModel.shouldStopTriage) { + // Navigate to results/possible conditions screen + context.navigateWithName(AppRoutes.possibleConditionsScreen); + return; } + + // Question is loaded, reset selection for new question + viewModel.resetTriageChoice(); } - _buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) { - return showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), + void _showEmergencyDialog() { + showCommonBottomSheetWithoutHeight( context, + title: "Emergency".needTranslation, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + loadingText: "Emergency evidence detected. Please seek immediate medical attention.".needTranslation, isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), - onConfirmTap: () => onConfirm(), + onConfirmTap: () { + Navigator.pop(context); + context.pop(); + }, ), - callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } + bool get isFirstQuestion => answeredEvidenceIds.isEmpty; + + void _onOptionSelected(int choiceIndex) { + viewModel.selectTriageChoice(choiceIndex); + } + + void _onPreviousPressed() { + context.pop(); + } + + void _onNextPressed() { + // Check if user has selected an option + if (viewModel.selectedTriageChoiceIndex == null) { + dialogService.showErrorBottomSheet(message: 'Please select an option before proceeding'.needTranslation); + return; + } + + // Get the selected choice from the current question + final currentQuestion = viewModel.currentTriageQuestion; + if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) { + dialogService.showErrorBottomSheet( + message: 'No question items available'.needTranslation, + ); + return; + } + + final questionItem = currentQuestion.items!.first; + if (questionItem.choices == null || viewModel.selectedTriageChoiceIndex! >= questionItem.choices!.length) { + dialogService.showErrorBottomSheet( + message: 'Invalid choice selection'.needTranslation, + ); + return; + } + + final selectedChoice = questionItem.choices![viewModel.selectedTriageChoiceIndex!]; + + final evidenceId = selectedChoice.label ?? ""; + if (evidenceId.isNotEmpty) { + answeredEvidenceIds.add(evidenceId); + } + + // Get all previous evidence IDs + List allEvidenceIds = viewModel.getAllEvidenceIds(); + allEvidenceIds.addAll(answeredEvidenceIds); + + log("allEvidences: ${allEvidenceIds.toString()}"); + + // Call API with updated evidence + viewModel.getDiagnosisForTriage( + age: viewModel.selectedAge!, + sex: viewModel.selectedGender!.toLowerCase(), + evidenceIds: allEvidenceIds, + language: viewModel.appState.isArabic() ? 'ar' : 'en', + onSuccess: (response) { + _handleTriageResponse(); + }, + onError: (error) { + dialogService.showErrorBottomSheet(message: error); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: "Triage".needTranslation, - // onLeadingTapped: () => _buildConfirmationBottomSheet( - // context: context, - // onConfirm: () => { - // context.pop(), - // context.pop(), - // }), - - leadingCallback: () => context.pop(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - _buildQuestionCard(), - ], + body: Consumer( + builder: (context, viewModel, child) { + // Show normal question UI + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Triage".needTranslation, + leadingCallback: () => _showConfirmationBeforeExit(context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + _buildQuestionCard(viewModel), + ], + ), + ), ), + _buildStickyBottomCard(context, viewModel), + ], + ); + }, + ), + ); + } + + Widget _buildLoadingShimmer() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + // Create 2-3 shimmer cards + ...List.generate(1, (index) { + return Padding( + padding: EdgeInsets.only(bottom: 16.h), + child: _buildShimmerCard(), + ); + }), + ], + ); + } + + Widget _buildShimmerCard() { + return Container( + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 24.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Shimmer title + Container( + height: 40.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24.r), ), + ).toShimmer2(isShow: true, radius: 24.r), + SizedBox(height: 16.h), + // Shimmer chips + Wrap( + runSpacing: 12.h, + spacing: 8.w, + children: List.generate(4, (index) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(24.r), + border: Border.all(color: AppColors.bottomNAVBorder, width: 1), + ), + child: Text( + 'Not Applicable Suggestion', + style: TextStyle(fontSize: 14.f, color: AppColors.textColor), + ), + ).toShimmer2(isShow: true, radius: 24.r); + }), ), - _buildStickyBottomCard(context), ], ), ); } - Widget _buildQuestionCard() { + Widget _buildErrorState() { + return CollapsingListView( + title: "Triage".needTranslation, + leadingCallback: () => context.pop(), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64.f, color: AppColors.errorColor), + SizedBox(height: 16.h), + "No question available".needTranslation.toText16(weight: FontWeight.w500), + SizedBox(height: 24.h), + CustomButton( + text: "Go Back".needTranslation, + onPressed: () => context.pop(), + backgroundColor: AppColors.primaryRedColor, + ).paddingSymmetrical(48.w, 0), + ], + ), + ), + ); + } + + void _showConfirmationBeforeExit(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation, + isShowActionButtons: true, + onCancelTap: () => Navigator.pop(context), + onConfirmTap: () { + Navigator.pop(context); + context.pop(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + Widget _buildQuestionCard(SymptomsCheckerViewModel viewModel) { + if (viewModel.isTriageDiagnosisLoading) { + return _buildLoadingShimmer(); + } + + if (viewModel.currentTriageQuestion == null) { + return Center( + child: "No question available".needTranslation.toText16(weight: FontWeight.w500), + ); + } + + final question = viewModel.currentTriageQuestion; + if (question == null || question.items == null || question.items!.isEmpty) { + return SizedBox.shrink(); + } + + final questionItem = question.items!.first; + final choices = questionItem.choices ?? []; + return AnimatedSwitcher( duration: const Duration(milliseconds: 400), transitionBuilder: (Widget child, Animation animation) { final offsetAnimation = Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeInOut, - )); + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeInOut, + ), + ); return SlideTransition( position: offsetAnimation, @@ -140,7 +324,7 @@ class _TriageScreenState extends State { ); }, child: Container( - key: ValueKey(currentQuestionIndex), + key: ValueKey(questionItem.id ?? answeredEvidenceIds.length.toString()), width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -148,11 +332,11 @@ class _TriageScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - currentQuestion.question.toText16(weight: FontWeight.w500), + (question.text ?? "").toText16(weight: FontWeight.w500), SizedBox(height: 24.h), - ...List.generate(currentQuestion.options.length, (index) { - bool selected = currentQuestion.selectedOptionIndex == index; - return _buildOptionItem(index, selected, currentQuestion.options[index].text); + ...List.generate(choices.length, (index) { + bool selected = viewModel.selectedTriageChoiceIndex == index; + return _buildOptionItem(index, selected, choices[index].label ?? ""); }), ], ), @@ -188,9 +372,21 @@ class _TriageScreenState extends State { ); } - Widget _buildStickyBottomCard(BuildContext context) { - final currentScore = TriageQuestionsData.calculateTotalScore(triageQuestions); - final suggestedCondition = TriageQuestionsData.getSuggestedCondition(currentScore); + Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) { + // Get the top condition with highest probability + final conditions = viewModel.currentConditions ?? []; + String suggestedCondition = "Analyzing..."; + double probability = 0.0; + + if (conditions.isNotEmpty) { + // Sort by probability descending + final sortedConditions = List.from(conditions); + sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); + + final topCondition = sortedConditions.first; + suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; + probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage + } return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -223,14 +419,14 @@ class _TriageScreenState extends State { ), SizedBox(height: 16.h), CustomRoundedProgressBar( - percentage: currentScore, + percentage: probability.toInt(), paddingBetween: 5.h, color: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.17), height: 8.h, titleWidget: RichText( text: TextSpan( - text: "$currentScore% ", + text: "${probability.toStringAsFixed(1)}% ", style: TextStyle( color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, @@ -256,7 +452,7 @@ class _TriageScreenState extends State { child: CustomButton( text: "Previous".needTranslation, onPressed: isFirstQuestion ? () {} : _onPreviousPressed, - isDisabled: isFirstQuestion, + isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, @@ -266,7 +462,8 @@ class _TriageScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: isLastQuestion ? "Finish".needTranslation : "Next".needTranslation, + text: "Next".needTranslation, + isDisabled: viewModel.isTriageDiagnosisLoading, onPressed: _onNextPressed, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart index d73f387..8366545 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -35,7 +33,6 @@ class AgeSelectionPage extends StatelessWidget { initialDate: symptomsViewModel.dateOfBirth ?? DateTime(2000, 1, 1), onDateChanged: (date) { symptomsViewModel.setDateOfBirth(date); - log('DOB saved: $date, Age: ${symptomsViewModel.selectedAge}'); }, ) ], diff --git a/lib/presentation/water_monitor/water_monitor_settings_screen.dart b/lib/presentation/water_monitor/water_monitor_settings_screen.dart index 1b783ca..3470344 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_screen.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_screen.dart @@ -10,6 +10,7 @@ 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'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; class WaterMonitorSettingsScreen extends StatefulWidget { @@ -20,6 +21,14 @@ class WaterMonitorSettingsScreen extends StatefulWidget { } class _WaterMonitorSettingsScreenState extends State { + late DialogService dialogService; + + @override + void initState() { + super.initState(); + dialogService = getIt.get(); + } + // No need to call initialize() here since it's already called in water_consumption_screen // The ViewModel is shared via Provider, so data is already loaded @@ -59,7 +68,6 @@ class _WaterMonitorSettingsScreenState extends State required Function(String) onSelected, bool useUpperCase = false, }) { - final dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutHWithChild( label: title.needTranslation, @@ -263,9 +271,20 @@ class _WaterMonitorSettingsScreenState extends State onPressed: () async { final success = await viewModel.saveSettings(); if (!success && viewModel.validationError != null) { - _showSnackBar(context, viewModel.validationError!); + dialogService.showErrorBottomSheet( + message: viewModel.validationError!, + ); } else if (success) { - _showSnackBar(context, "Settings saved successfully"); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget( + loadingText: "Settings saved successfully".needTranslation, + ), + callBackFunc: () {}, + isCloseButtonVisible: false, + isDismissible: true, + isFullScreen: false, + ); } }, borderRadius: 12.r, @@ -340,16 +359,4 @@ class _WaterMonitorSettingsScreenState extends State ), ); } - - // Show snackbar for validation errors and success messages - void _showSnackBar(BuildContext context, String message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - behavior: SnackBarBehavior.floating, - backgroundColor: message.contains('successfully') ? Colors.green : AppColors.errorColor, - ), - ); - } } diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 497a009..3c009f3 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -62,18 +62,16 @@ class DialogServiceImp implements DialogService { message: message, showCancel: onCancelPressed != null ? true : false, onOkPressed: () { - print('ok button is pressed'); if (onOkPressed != null) { - print('onOkPressed is not null'); onOkPressed(); - }else { + } else { Navigator.pop(context); } }, onCancelPressed: () { if (onCancelPressed != null) { onCancelPressed(); - }else { + } else { Navigator.pop(context); } }, @@ -108,7 +106,8 @@ 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, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( @@ -162,7 +161,8 @@ class DialogServiceImp implements DialogService { } @override - Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { + Future showPhoneNumberPickerSheet( + {String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight(context, @@ -184,7 +184,8 @@ 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, required Function() onOkPressed, Function()? onCancelPressed}) { return Column( children: [ (message).toText16(isBold: false, color: AppColors.textColor), @@ -239,7 +240,8 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin ); } -Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) { +Widget showPhoneNumberPickerWidget( + {required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) { return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { return Column( children: [ diff --git a/services/api.ts b/services/api.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/services/api.ts @@ -0,0 +1 @@ + diff --git a/types/user.ts b/types/user.ts new file mode 100644 index 0000000..a2158ef --- /dev/null +++ b/types/user.ts @@ -0,0 +1,57 @@ +export interface UserName { + first_name: string; + middle_name: string; + last_name: string; + first_name_ar: string; + middle_name_ar: string; + last_name_ar: string; +} + +export interface UserDetails { + FileNo: string; + national_id: string; + email: string; + date_of_birth: string; + date_of_birth_hijri: string | null; + age: number; + name: UserName; + marital_status: number; + marital_status_code: string | null; + nationality: string; + nationality_iso_code: string; + occupation: string | null; + id_type: number; + gender: number; + jwt_token: string | null; + country_dial_code: string; + phone_no: string; +} + +export interface TokenDetails { + id: string; + auth_token: string; + expires_in: number; +} + +export interface GetUserDetailsResponse { + tokenDetails: TokenDetails; + userDetails: UserDetails; + sessionId: string; +} + +// Simplified User type for app usage +export interface User { + id: string; + fileNo: string; + nationalId: string; + email: string; + name: string; + nameAr: string; + phoneNo: string; + dateOfBirth: string; + nationality: string; + gender: number; + authToken: string; + sessionId: string; +} + diff --git a/utils/apiHelpers.ts b/utils/apiHelpers.ts new file mode 100644 index 0000000..3fae819 --- /dev/null +++ b/utils/apiHelpers.ts @@ -0,0 +1,18 @@ +export function safeJsonParse(data: any): T { + if (typeof data === 'string') { + try { + return JSON.parse(data); + } catch (error) { + console.error('Failed to parse JSON string:', error); + throw new Error('Invalid JSON response from server'); + } + } + return data; +} + +export function logApiResponse(endpoint: string, response: any) { + if (__DEV__) { + console.log(`[API Response - ${endpoint}]`, JSON.stringify(response, null, 2)); + } +} + From 8c91b6830ae1af16c67e290ba07ca41045813823 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 5 Jan 2026 15:32:10 +0300 Subject: [PATCH 14/46] Triage Completed. Explain API left --- contexts/AuthContext.tsx | 31 --- lib/core/api/api_client.dart | 2 +- .../symptoms_checker_repo.dart | 85 +++++--- .../symptoms_checker_view_model.dart | 155 +++++++++++--- .../symptoms_checker/triage_screen.dart | 202 ++++++++++++------ services/api.ts | 1 - types/user.ts | 57 ----- utils/apiHelpers.ts | 18 -- 8 files changed, 320 insertions(+), 231 deletions(-) delete mode 100644 contexts/AuthContext.tsx delete mode 100644 services/api.ts delete mode 100644 types/user.ts delete mode 100644 utils/apiHelpers.ts diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx deleted file mode 100644 index 48571a8..0000000 --- a/contexts/AuthContext.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// ...existing imports... -import { apiService } from '../services/api'; - -// ...existing code... - -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - // ...existing state... - - const login = async (nationalId: string, password: string) => { - try { - setIsLoading(true); - - // Call the real API - const user = await apiService.getUserDetails(nationalId, password); - - // Store user data - await AsyncStorage.setItem('user', JSON.stringify(user)); - await AsyncStorage.setItem('authToken', user.authToken); - - setUser(user); - } catch (error) { - console.error('Login error:', error); - throw new Error('Invalid credentials or network error'); - } finally { - setIsLoading(false); - } - }; - - // ...existing code... -}; - diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 039787b..f366329 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; log("uri: ${Uri.parse(url.trim())}"); log("body: ${json.encode(body)}"); - log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 2d9a99b..c55c2f0 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -12,18 +12,21 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class SymptomsCheckerRepo { - Future>> getUserDetails({ + Future>> + getUserDetails({ required String userName, required String password, }); - Future>> getBodySymptomsByName({ + Future>> + getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, }); - Future>> getRiskFactors({ + Future>> + getRiskFactors({ required int age, required String sex, required List evidenceIds, @@ -33,7 +36,8 @@ abstract class SymptomsCheckerRepo { required String sessionId, }); - Future>> getSuggestions({ + Future>> + getSuggestions({ required int age, required String sex, required List evidenceIds, @@ -43,10 +47,12 @@ abstract class SymptomsCheckerRepo { required int gender, }); - Future>> getDiagnosisForTriage({ + Future>> + getDiagnosisForTriage({ required int age, required String sex, required List evidenceIds, + List>? triageEvidence, required String language, required String userSessionToken, required int gender, @@ -58,10 +64,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { final ApiClient apiClient; final LoggerService loggerService; - SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService}); + SymptomsCheckerRepoImp( + {required this.apiClient, required this.loggerService}); @override - Future>> getUserDetails({ + Future>> + getUserDetails({ required String userName, required String password, }) async { @@ -84,9 +92,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = SymptomsUserDetailsResponseModel.fromJson(responseData); + SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = + SymptomsUserDetailsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -113,7 +123,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getBodySymptomsByName({ + Future>> + getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, @@ -143,7 +154,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response); + BodySymptomResponseModel bodySymptomResponse = + BodySymptomResponseModel.fromJson(response); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -152,7 +164,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { data: bodySymptomResponse, ); } catch (e, stackTrace) { - loggerService.logError("Error parsing GetBodySymptomsByName response: $e"); + loggerService + .logError("Error parsing GetBodySymptomsByName response: $e"); loggerService.logError("StackTrace: $stackTrace"); failure = DataParsingFailure(e.toString()); } @@ -170,7 +183,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getRiskFactors({ + Future>> + getRiskFactors({ required int age, required String sex, required List evidenceIds, @@ -211,9 +225,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = + RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -239,22 +255,37 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } } - @override - Future>> getDiagnosisForTriage({ + Future>> + getDiagnosisForTriage({ required int age, required String sex, required List evidenceIds, + List>? + triageEvidence, // Additional triage-specific evidence required String language, required String userSessionToken, required int gender, required String sessionId, }) async { + // Build evidence list: combine initial symptoms with triage evidence + List> evidenceList = []; + + // Add initial evidence as simple IDs + for (var id in evidenceIds) { + evidenceList.add({"id": id}); + } + + // Add triage evidence with proper format (id, choice_id, source) + if (triageEvidence != null && triageEvidence.isNotEmpty) { + evidenceList.addAll(triageEvidence); + } + final Map body = { "age": { "value": age, }, "sex": sex, - "evidence": evidenceIds.map((id) => {"id": id}).toList(), + "evidence": evidenceList, "language": language, "suggest_method": "diagnosis", "generalId": sessionId, @@ -282,11 +313,13 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; final updatedResponseData = responseData['dataDetails']; - TriageDataDetails riskFactorsResponse = TriageDataDetails.fromJson(updatedResponseData); + TriageDataDetails riskFactorsResponse = + TriageDataDetails.fromJson(updatedResponseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -295,7 +328,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { data: riskFactorsResponse, ); } catch (e, stackTrace) { - loggerService.logError("Error parsing getDiagnosisForTriage response: $e"); + loggerService + .logError("Error parsing getDiagnosisForTriage response: $e"); loggerService.logError("StackTrace: $stackTrace"); failure = DataParsingFailure(e.toString()); } @@ -313,7 +347,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getSuggestions({ + Future>> + getSuggestions({ required int age, required String sex, required List evidenceIds, @@ -354,9 +389,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = + RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index b41bab2..73768fe 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -47,7 +47,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { TriageDataDetails? triageDataDetails; // Triage state - int? _selectedTriageChoiceIndex; + int? + _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility + final Map _selectedTriageChoicesByItemId = + {}; // Map of itemId -> choiceIndex for multi-item questions + final List> _triageEvidenceList = + []; // Store triage evidence with proper format + int _triageQuestionCount = 0; // Track number of triage questions answered // Selected risk factors tracking final Set _selectedRiskFactorIds = {}; @@ -60,7 +66,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // User Info Flow State int _userInfoCurrentPage = 0; - bool _isSinglePageEditMode = false; // Track if editing single page or full flow + bool _isSinglePageEditMode = + false; // Track if editing single page or full flow String? _selectedGender; DateTime? _dateOfBirth; int? _selectedAge; @@ -100,14 +107,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { String? get tooltipOrganId => _tooltipOrganId; - String get currentSessionAuthToken => symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? ""; + String get currentSessionAuthToken => + symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? ""; - String get currentSessionId => symptomsUserDetailsResponseModel?.sessionId ?? ""; + String get currentSessionId => + symptomsUserDetailsResponseModel?.sessionId ?? ""; // Triage-related getters bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false; - bool get hasEmergencyEvidence => triageDataDetails?.hasEmergencyEvidence ?? false; + bool get hasEmergencyEvidence => + triageDataDetails?.hasEmergencyEvidence ?? false; String? get currentInterviewToken => triageDataDetails?.interviewToken; @@ -117,8 +127,34 @@ class SymptomsCheckerViewModel extends ChangeNotifier { int? get selectedTriageChoiceIndex => _selectedTriageChoiceIndex; + /// Get the number of triage questions answered + int get triageQuestionCount => _triageQuestionCount; + + /// Get choice index for a specific item + int? getTriageChoiceForItem(String itemId) { + return _selectedTriageChoicesByItemId[itemId]; + } + + /// Check if all items in current question have been answered + bool get areAllTriageItemsAnswered { + if (currentTriageQuestion?.items == null || + currentTriageQuestion!.items!.isEmpty) { + return false; + } + + // Check if we have an answer for each item + for (var item in currentTriageQuestion!.items!) { + if (item.id != null && + !_selectedTriageChoicesByItemId.containsKey(item.id)) { + return false; + } + } + return true; + } + /// Get organs for current view - List get currentOrgans => OrganData.getOrgansForView(_currentView); + List get currentOrgans => + OrganData.getOrgansForView(_currentView); /// Get all selected organs from both views List get selectedOrgans { @@ -126,7 +162,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ...OrganData.frontViewOrgans, ...OrganData.backViewOrgans, ]; - return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList(); + return allOrgans + .where((organ) => _selectedOrganIds.contains(organ.id)) + .toList(); } /// Check if any organs are selected @@ -143,11 +181,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } int get totalSelectedSymptomsCount { - return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length); + return _selectedSymptomsByOrgan.values + .fold(0, (sum, symptomIds) => sum + symptomIds.length); } bool get hasSelectedSymptoms { - return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty); + return _selectedSymptomsByOrgan.values + .any((symptomIds) => symptomIds.isNotEmpty); } /// Get risk factors list @@ -173,7 +213,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } void toggleView() { - _currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front; + _currentView = + _currentView == BodyView.front ? BodyView.back : BodyView.front; notifyListeners(); } @@ -276,7 +317,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return; } - List organNames = selectedOrgans.map((organ) => organ.name).toList(); + List organNames = + selectedOrgans.map((organ) => organ.name).toList(); await getBodySymptomsByName( organNames: organNames, @@ -326,7 +368,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } } - if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { + if (matchingOrganId != null && + _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!; if (organResult.bodySymptoms != null) { @@ -377,7 +420,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List getAllSelectedRiskFactors() { - return riskFactorsList.where((factor) => factor.id != null && _selectedRiskFactorIds.contains(factor.id)).toList(); + return riskFactorsList + .where((factor) => + factor.id != null && _selectedRiskFactorIds.contains(factor.id)) + .toList(); } /// Clear all risk factor selections @@ -410,7 +456,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = + selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); await getRiskFactors( age: _selectedAge!, @@ -468,8 +515,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1 && apiResponse.data != null) { riskFactorsResponse = apiResponse.data; - if (riskFactorsResponse != null && riskFactorsResponse!.dataDetails != null) { - RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel( + if (riskFactorsResponse != null && + riskFactorsResponse!.dataDetails != null) { + RiskAndSuggestionsItemModel riskFactorItem = + RiskAndSuggestionsItemModel( id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", @@ -523,7 +572,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List getAllSelectedSuggestions() { - return suggestionsList.where((factor) => factor.id != null && _selectedSuggestionsIds.contains(factor.id)).toList(); + return suggestionsList + .where((factor) => + factor.id != null && _selectedSuggestionsIds.contains(factor.id)) + .toList(); } /// Clear all risk factor selections @@ -538,15 +590,20 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Add selected symptoms final selectedSymptoms = getAllSelectedSymptoms(); - evidenceIds.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!)); + evidenceIds + .addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!)); // Add selected risk factors (excluding "not_applicable") final selectedRiskFactors = getAllSelectedRiskFactors(); - evidenceIds.addAll(selectedRiskFactors.where((rf) => rf.id != null && rf.id != "not_applicable").map((rf) => rf.id!)); + evidenceIds.addAll(selectedRiskFactors + .where((rf) => rf.id != null && rf.id != "not_applicable") + .map((rf) => rf.id!)); // Add selected suggestions (excluding "not_applicable") final selectedSuggestions = getAllSelectedSuggestions(); - evidenceIds.addAll(selectedSuggestions.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!)); + evidenceIds.addAll(selectedSuggestions + .where((s) => s.id != null && s.id != "not_applicable") + .map((s) => s.id!)); return evidenceIds; } @@ -575,13 +632,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = + selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); // Get all selected symptoms final selectedRisks = getAllSelectedRiskFactors(); if (selectedRisks.isNotEmpty) { - List evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList(); + List evidenceRisksIds = selectedRisks + .where((s) => s.id != null && s.id != "not_applicable") + .map((s) => s.id!) + .toList(); evidenceIds.addAll(evidenceRisksIds); } @@ -639,8 +700,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1 && apiResponse.data != null) { suggestionsResponse = apiResponse.data; - if (suggestionsResponse != null && suggestionsResponse!.dataDetails != null) { - RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel( + if (suggestionsResponse != null && + suggestionsResponse!.dataDetails != null) { + RiskAndSuggestionsItemModel riskFactorItem = + RiskAndSuggestionsItemModel( id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", @@ -669,6 +732,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { required int age, required String sex, required List evidenceIds, + List>? triageEvidence, required String language, Function(TriageDataDetails)? onSuccess, Function(String)? onError, @@ -680,6 +744,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { age: age, sex: sex, evidenceIds: evidenceIds, + triageEvidence: triageEvidence, language: language, sessionId: currentSessionId, userSessionToken: currentSessionAuthToken, @@ -755,15 +820,44 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } - /// Select a triage choice option + /// Select a triage choice option (for backward compatibility with single-item questions) void selectTriageChoice(int choiceIndex) { _selectedTriageChoiceIndex = choiceIndex; notifyListeners(); } - /// Reset triage choice selection + /// Select a choice for a specific item (for multi-item questions) + void selectTriageChoiceForItem(String itemId, int choiceIndex) { + _selectedTriageChoicesByItemId[itemId] = choiceIndex; + notifyListeners(); + } + + /// Reset triage choice selection and increment question count void resetTriageChoice() { _selectedTriageChoiceIndex = null; + _selectedTriageChoicesByItemId.clear(); + _triageQuestionCount++; // Increment question count + notifyListeners(); + } + + /// Add triage evidence in the proper format + void addTriageEvidence(String itemId, String choiceId) { + _triageEvidenceList.add({ + "id": itemId, + "choice_id": choiceId, + "source": "triage", + }); + notifyListeners(); + } + + /// Get all triage evidence + List> getTriageEvidence() { + return List.from(_triageEvidenceList); + } + + /// Clear triage evidence + void clearTriageEvidence() { + _triageEvidenceList.clear(); notifyListeners(); } @@ -773,6 +867,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { _selectedSymptomsByOrgan.clear(); _selectedRiskFactorIds.clear(); _selectedSuggestionsIds.clear(); + _triageEvidenceList.clear(); + _selectedTriageChoicesByItemId.clear(); + _triageQuestionCount = 0; // Reset question count bodySymptomResponse = null; riskFactorsResponse = null; suggestionsResponse = null; @@ -844,7 +941,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Calculate age from date of birth final now = DateTime.now(); int age = now.year - dateOfBirth.year; - if (now.month < dateOfBirth.month || (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) { + if (now.month < dateOfBirth.month || + (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) { age--; } _selectedAge = age; @@ -891,7 +989,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { }) async { isBodySymptomsLoading = true; notifyListeners(); - final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password); + final result = await symptomsCheckerRepo.getUserDetails( + userName: userName, password: password); result.fold( (failure) async { diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 8262c67..ba159bd 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; class TriageScreen extends StatefulWidget { @@ -27,7 +29,6 @@ class TriageScreen extends StatefulWidget { } class _TriageScreenState extends State { - List answeredEvidenceIds = []; // Track user's answers late SymptomsCheckerViewModel viewModel; late DialogService dialogService; @@ -58,43 +59,96 @@ class _TriageScreenState extends State { } void _handleTriageResponse() { + // Case 1: Emergency evidence detected if (viewModel.hasEmergencyEvidence) { _showEmergencyDialog(); return; } - if (viewModel.shouldStopTriage) { + // Get the highest probability condition + final conditions = viewModel.currentConditions ?? []; + double highestProbability = 0.0; + + if (conditions.isNotEmpty) { + final sortedConditions = List.from(conditions); + sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); + highestProbability = (sortedConditions.first.probability ?? 0.0) * 100; + } + + // Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered + if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) { // Navigate to results/possible conditions screen context.navigateWithName(AppRoutes.possibleConditionsScreen); return; } - // Question is loaded, reset selection for new question + // Continue triage - question is loaded, reset selection for new question viewModel.resetTriageChoice(); } void _showEmergencyDialog() { showCommonBottomSheetWithoutHeight( context, - title: "Emergency".needTranslation, - child: Utils.getWarningWidget( - loadingText: "Emergency evidence detected. Please seek immediate medical attention.".needTranslation, - isShowActionButtons: true, - onCancelTap: () => Navigator.pop(context), - onConfirmTap: () { - Navigator.pop(context); - context.pop(); - }, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor, + borderRadius: 24.h, + ), + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".toText14(), + Utils.buildSvgWithAssets( + icon: AppAssets.cancel_circle_icon, + iconColor: AppColors.whiteColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ).onPress(() { + Navigator.of(context).pop(); + }), + ], + ), + Lottie.asset(AppAnimations.ambulanceAlert, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + SizedBox(height: 8.h), + "Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + SizedBox(height: 8.h), + "Emergency evidence detected. Please seek medical attention." + .needTranslation + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async => Navigator.of(context).pop(), + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.whiteColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + ), + SizedBox(height: 8.h), + ], + ), + ), ), isFullScreen: false, - isCloseButtonVisible: true, + isCloseButtonVisible: false, + hasBottomPadding: false, + backgroundColor: AppColors.primaryRedColor, + callBackFunc: () {}, ); } - bool get isFirstQuestion => answeredEvidenceIds.isEmpty; + bool get isFirstQuestion => viewModel.getTriageEvidence().isEmpty; - void _onOptionSelected(int choiceIndex) { - viewModel.selectTriageChoice(choiceIndex); + void _onOptionSelectedForItem(String itemId, int choiceIndex) { + viewModel.selectTriageChoiceForItem(itemId, choiceIndex); } void _onPreviousPressed() { @@ -102,13 +156,6 @@ class _TriageScreenState extends State { } void _onNextPressed() { - // Check if user has selected an option - if (viewModel.selectedTriageChoiceIndex == null) { - dialogService.showErrorBottomSheet(message: 'Please select an option before proceeding'.needTranslation); - return; - } - - // Get the selected choice from the current question final currentQuestion = viewModel.currentTriageQuestion; if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) { dialogService.showErrorBottomSheet( @@ -117,32 +164,43 @@ class _TriageScreenState extends State { return; } - final questionItem = currentQuestion.items!.first; - if (questionItem.choices == null || viewModel.selectedTriageChoiceIndex! >= questionItem.choices!.length) { - dialogService.showErrorBottomSheet( - message: 'Invalid choice selection'.needTranslation, - ); + // Check if all items have been answered + if (!viewModel.areAllTriageItemsAnswered) { + dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation); return; } - final selectedChoice = questionItem.choices![viewModel.selectedTriageChoiceIndex!]; + // Collect all evidence from all items + for (var item in currentQuestion.items!) { + final itemId = item.id ?? ""; + if (itemId.isEmpty) continue; - final evidenceId = selectedChoice.label ?? ""; - if (evidenceId.isNotEmpty) { - answeredEvidenceIds.add(evidenceId); + final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId); + if (selectedChoiceIndex == null) continue; + + if (item.choices != null && selectedChoiceIndex < item.choices!.length) { + final selectedChoice = item.choices![selectedChoiceIndex]; + final choiceId = selectedChoice.id ?? ""; + + if (choiceId.isNotEmpty) { + viewModel.addTriageEvidence(itemId, choiceId); + } + } } - // Get all previous evidence IDs - List allEvidenceIds = viewModel.getAllEvidenceIds(); - allEvidenceIds.addAll(answeredEvidenceIds); + // Get all evidence: initial symptoms + risk factors + suggestions + triage evidence + List initialEvidenceIds = viewModel.getAllEvidenceIds(); + List> triageEvidence = viewModel.getTriageEvidence(); - log("allEvidences: ${allEvidenceIds.toString()}"); + log("initialEvidenceIds: ${initialEvidenceIds.toString()}"); + log("triageEvidence: ${triageEvidence.toString()}"); // Call API with updated evidence viewModel.getDiagnosisForTriage( age: viewModel.selectedAge!, sex: viewModel.selectedGender!.toLowerCase(), - evidenceIds: allEvidenceIds, + evidenceIds: initialEvidenceIds, + triageEvidence: triageEvidence, language: viewModel.appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { _handleTriageResponse(); @@ -241,29 +299,6 @@ class _TriageScreenState extends State { ); } - Widget _buildErrorState() { - return CollapsingListView( - title: "Triage".needTranslation, - leadingCallback: () => context.pop(), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.error_outline, size: 64.f, color: AppColors.errorColor), - SizedBox(height: 16.h), - "No question available".needTranslation.toText16(weight: FontWeight.w500), - SizedBox(height: 24.h), - CustomButton( - text: "Go Back".needTranslation, - onPressed: () => context.pop(), - backgroundColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.w, 0), - ], - ), - ), - ); - } - void _showConfirmationBeforeExit(BuildContext context) { showCommonBottomSheetWithoutHeight( title: LocaleKeys.notice.tr(context: context), @@ -299,9 +334,6 @@ class _TriageScreenState extends State { return SizedBox.shrink(); } - final questionItem = question.items!.first; - final choices = questionItem.choices ?? []; - return AnimatedSwitcher( duration: const Duration(milliseconds: 400), transitionBuilder: (Widget child, Animation animation) { @@ -324,7 +356,7 @@ class _TriageScreenState extends State { ); }, child: Container( - key: ValueKey(questionItem.id ?? answeredEvidenceIds.length.toString()), + key: ValueKey(question.items!.first.id ?? viewModel.getTriageEvidence().length.toString()), width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -332,11 +364,36 @@ class _TriageScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (question.text ?? "").toText16(weight: FontWeight.w500), + // Main question text + (question.text ?? "").toText16(weight: FontWeight.w600, color: AppColors.textColor), SizedBox(height: 24.h), - ...List.generate(choices.length, (index) { - bool selected = viewModel.selectedTriageChoiceIndex == index; - return _buildOptionItem(index, selected, choices[index].label ?? ""); + + // Show all items with dividers + ...List.generate(question.items!.length, (itemIndex) { + final item = question.items![itemIndex]; + final itemId = item.id ?? ""; + final choices = item.choices ?? []; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Item name (sub-question) + (item.name ?? "").toText14(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 8.h), + // Choices for this item + ...List.generate(choices.length, (choiceIndex) { + bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex; + return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); + }), + + // Add divider between items (but not after the last one) + if (itemIndex < question.items!.length - 1) ...[ + SizedBox(height: 8.h), + Divider(color: AppColors.bottomNAVBorder, thickness: 1), + SizedBox(height: 10.h), + ], + ], + ); }), ], ), @@ -344,9 +401,9 @@ class _TriageScreenState extends State { ); } - Widget _buildOptionItem(int index, bool selected, String optionText) { + Widget _buildOptionItem(String itemId, int choiceIndex, bool selected, String optionText) { return GestureDetector( - onTap: () => _onOptionSelected(index), + onTap: () => _onOptionSelectedForItem(itemId, choiceIndex), child: Container( margin: EdgeInsets.only(bottom: 12.h), child: Row( @@ -365,7 +422,7 @@ class _TriageScreenState extends State { child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, ), SizedBox(width: 12.w), - Expanded(child: optionText.toText14(weight: FontWeight.w500)), + Expanded(child: optionText.toText13(weight: FontWeight.w500)), ], ), ), @@ -387,6 +444,7 @@ class _TriageScreenState extends State { suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage } + // final bool isHighConfidence = probability >= 70.0; return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -445,6 +503,8 @@ class _TriageScreenState extends State { ), ), ), + // Show high confidence message + SizedBox(height: 12.h), Row( children: [ diff --git a/services/api.ts b/services/api.ts deleted file mode 100644 index 8b13789..0000000 --- a/services/api.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/types/user.ts b/types/user.ts deleted file mode 100644 index a2158ef..0000000 --- a/types/user.ts +++ /dev/null @@ -1,57 +0,0 @@ -export interface UserName { - first_name: string; - middle_name: string; - last_name: string; - first_name_ar: string; - middle_name_ar: string; - last_name_ar: string; -} - -export interface UserDetails { - FileNo: string; - national_id: string; - email: string; - date_of_birth: string; - date_of_birth_hijri: string | null; - age: number; - name: UserName; - marital_status: number; - marital_status_code: string | null; - nationality: string; - nationality_iso_code: string; - occupation: string | null; - id_type: number; - gender: number; - jwt_token: string | null; - country_dial_code: string; - phone_no: string; -} - -export interface TokenDetails { - id: string; - auth_token: string; - expires_in: number; -} - -export interface GetUserDetailsResponse { - tokenDetails: TokenDetails; - userDetails: UserDetails; - sessionId: string; -} - -// Simplified User type for app usage -export interface User { - id: string; - fileNo: string; - nationalId: string; - email: string; - name: string; - nameAr: string; - phoneNo: string; - dateOfBirth: string; - nationality: string; - gender: number; - authToken: string; - sessionId: string; -} - diff --git a/utils/apiHelpers.ts b/utils/apiHelpers.ts deleted file mode 100644 index 3fae819..0000000 --- a/utils/apiHelpers.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function safeJsonParse(data: any): T { - if (typeof data === 'string') { - try { - return JSON.parse(data); - } catch (error) { - console.error('Failed to parse JSON string:', error); - throw new Error('Invalid JSON response from server'); - } - } - return data; -} - -export function logApiResponse(endpoint: string, response: any) { - if (__DEV__) { - console.log(`[API Response - ${endpoint}]`, JSON.stringify(response, null, 2)); - } -} - From 4ee0d1d3c8dd76db95e70759798cf453c9a31f0e Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 5 Jan 2026 15:47:41 +0300 Subject: [PATCH 15/46] only explain api left. --- .../symptoms_checker/user_info_selection.dart | 121 +++++++++++++----- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index 91f3d36..e5163f5 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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'; @@ -17,7 +18,8 @@ class UserInfoSelectionScreen extends StatefulWidget { const UserInfoSelectionScreen({super.key}); @override - State createState() => _UserInfoSelectionScreenState(); + State createState() => + _UserInfoSelectionScreenState(); } class _UserInfoSelectionScreenState extends State { @@ -51,7 +53,7 @@ class _UserInfoSelectionScreenState extends State { if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) { try { - DateTime dob = DateTime.parse(user.dateofBirth!); + DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!); viewModel.setDateOfBirth(dob); } catch (e) { // If date parsing fails, ignore and let user fill manually @@ -85,19 +87,25 @@ class _UserInfoSelectionScreenState extends State { width: 40.h, margin: EdgeInsets.only(right: 10.h), padding: EdgeInsets.all(8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), - child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + borderRadius: 12.r, color: AppColors.greyColor), + child: Utils.buildSvgWithAssets( + icon: leadingIcon, iconColor: iconColor)), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(weight: FontWeight.w500), - subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + subTitle.toText14( + color: AppColors.primaryRedColor, + weight: FontWeight.w500), ], ), ], ), ), - Utils.buildSvgWithAssets(icon: trailingIcon, height: 24.h, width: 24.h), + Utils.buildSvgWithAssets( + icon: trailingIcon, height: 24.h, width: 24.h), ], ), ); @@ -114,8 +122,27 @@ class _UserInfoSelectionScreenState extends State { AppState appState = getIt.get(); String name = ""; + int? userAgeFromDOB; + if (appState.isAuthenticated) { - name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} "; + final user = appState.getAuthenticatedUser(); + name = "${user!.firstName!} ${user.lastName!} "; + + // Calculate age from authenticated user's DOB if available + if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) { + try { + DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!); + final now = DateTime.now(); + int age = now.year - dob.year; + if (now.month < dob.month || + (now.month == dob.month && now.day < dob.day)) { + age--; + } + userAgeFromDOB = age; + } catch (e) { + // If date parsing fails, ignore + } + } } else { name = "Guest"; } @@ -132,12 +159,15 @@ class _UserInfoSelectionScreenState extends State { // Get display values String genderText = viewModel.selectedGender ?? "Not set"; - // Show age calculated from DOB, not the DOB itself - String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set"; - String heightText = - viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set"; - String weightText = - viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : "Not set"; + // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) + int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; + String ageText = displayAge != null ? "$displayAge Years" : "Not set"; + String heightText = viewModel.selectedHeight != null + ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" + : "Not set"; + String weightText = viewModel.selectedWeight != null + ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" + : "Not set"; return Column( children: [ @@ -150,11 +180,17 @@ class _UserInfoSelectionScreenState extends State { children: [ Container( width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r), + padding: EdgeInsets.symmetric( + vertical: 24.h, horizontal: 16.w), child: Column( children: [ - "Hello $name, Is your information up to date?".needTranslation.toText18( + "Hello $name, Is your information up to date?" + .needTranslation + .toText18( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -165,8 +201,10 @@ class _UserInfoSelectionScreenState extends State { title: "Gender".needTranslation, subTitle: genderText, onTap: () { - viewModel.setUserInfoPage(0, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); + viewModel.setUserInfoPage(0, + isSinglePageEdit: true); + context.navigateWithName( + AppRoutes.userInfoFlowManager); }, trailingIcon: AppAssets.edit_icon, ), @@ -178,8 +216,10 @@ class _UserInfoSelectionScreenState extends State { subTitle: ageText, iconColor: AppColors.greyTextColor, onTap: () { - viewModel.setUserInfoPage(1, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); + viewModel.setUserInfoPage(1, + isSinglePageEdit: true); + context.navigateWithName( + AppRoutes.userInfoFlowManager); }, trailingIcon: AppAssets.edit_icon, ), @@ -190,8 +230,10 @@ class _UserInfoSelectionScreenState extends State { title: "Height".needTranslation, subTitle: heightText, onTap: () { - viewModel.setUserInfoPage(2, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); + viewModel.setUserInfoPage(2, + isSinglePageEdit: true); + context.navigateWithName( + AppRoutes.userInfoFlowManager); }, trailingIcon: AppAssets.edit_icon, ), @@ -202,8 +244,10 @@ class _UserInfoSelectionScreenState extends State { title: "Weight".needTranslation, subTitle: weightText, onTap: () { - viewModel.setUserInfoPage(3, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); + viewModel.setUserInfoPage(3, + isSinglePageEdit: true); + context.navigateWithName( + AppRoutes.userInfoFlowManager); }, trailingIcon: AppAssets.edit_icon, ), @@ -225,7 +269,8 @@ class _UserInfoSelectionScreenState extends State { Widget _buildBottomCard(BuildContext context, bool hasEmptyFields) { return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 24.r), child: SafeArea( top: false, child: Column( @@ -240,10 +285,13 @@ class _UserInfoSelectionScreenState extends State { icon: AppAssets.edit_icon, iconColor: AppColors.primaryRedColor, onPressed: () { - context.read().setUserInfoPage(0, isSinglePageEdit: false); + context + .read() + .setUserInfoPage(0, isSinglePageEdit: false); context.navigateWithName(AppRoutes.userInfoFlowManager); }, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), + backgroundColor: + AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 16.f, @@ -254,13 +302,22 @@ class _UserInfoSelectionScreenState extends State { child: CustomButton( text: "Yes, It is".needTranslation, icon: AppAssets.tickIcon, - iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, + iconColor: hasEmptyFields + ? AppColors.greyTextColor + : AppColors.whiteColor, onPressed: hasEmptyFields ? () {} // Empty function for disabled state - : () => context.navigateWithName(AppRoutes.organSelectorPage), - backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor, - borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor, - textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, + : () => context + .navigateWithName(AppRoutes.organSelectorPage), + backgroundColor: hasEmptyFields + ? AppColors.greyLightColor + : AppColors.primaryRedColor, + borderColor: hasEmptyFields + ? AppColors.greyLightColor + : AppColors.primaryRedColor, + textColor: hasEmptyFields + ? AppColors.greyTextColor + : AppColors.whiteColor, fontSize: 16.f, ), ), From cc255e9c9ab93df54e5d0ab2e3d4b33710935f7c Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 7 Jan 2026 15:57:32 +0300 Subject: [PATCH 16/46] vital sign detail page --- .../vital_sign/vital_sign_details_page.dart | 620 ++++++++++++++++++ .../vital_sign/vital_sign_page.dart | 80 ++- 2 files changed, 693 insertions(+), 7 deletions(-) create mode 100644 lib/presentation/vital_sign/vital_sign_details_page.dart diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart new file mode 100644 index 0000000..fbbea64 --- /dev/null +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -0,0 +1,620 @@ +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/common_models/data_points.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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.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/graph/custom_graph.dart'; +import 'package:provider/provider.dart'; + +/// Which vital sign is being shown in the details screen. +enum VitalSignMetric { + bmi, + height, + weight, + bloodPressure, + temperature, + heartRate, + respiratoryRate, +} + +class VitalSignDetailsArgs { + final VitalSignMetric metric; + final String title; + final String icon; + final String unit; + + /// Optional bounds used for graph shading and labels. + final double? low; + final double? high; + + const VitalSignDetailsArgs({ + required this.metric, + required this.title, + required this.icon, + required this.unit, + this.low, + this.high, + }); +} + +class VitalSignDetailsPage extends StatefulWidget { + final VitalSignDetailsArgs args; + + const VitalSignDetailsPage({super.key, required this.args}); + + @override + State createState() => _VitalSignDetailsPageState(); +} + +class _VitalSignDetailsPageState extends State { + bool _isGraphVisible = true; + + VitalSignDetailsArgs get args => widget.args; + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: 'Vital Sign Details'.needTranslation, + child: Consumer( + builder: (context, viewModel, child) { + final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; + + final history = _buildSeries(viewModel.vitalSignList, args); + final latestValueText = _latestValueText(latest); + final status = _statusForLatest(latest); + final scheme = VitalSignUiModel.scheme(status: status, label: args.title); + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _headerCard( + context, + title: args.title, + icon: args.icon, + valueText: latestValueText, + status: status, + scheme: scheme, + latestDate: latest?.vitalSignDate, + ), + SizedBox(height: 16.h), + + _whatIsThisResultCard(context), + SizedBox(height: 16.h), + + _historyCard(context, history: history), + SizedBox(height: 16.h), + + _nextStepsCard(context), + SizedBox(height: 32.h), + ], + ).paddingAll(24.h), + ); + }, + ), + ); + } + + Widget _headerCard( + BuildContext context, { + required String title, + required String icon, + required String valueText, + required String? status, + required VitalSignUiModel scheme, + required DateTime? latestDate, + }) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(10.h), + decoration: BoxDecoration( + color: scheme.iconBg, + borderRadius: BorderRadius.circular(12.r), + ), + child: Utils.buildSvgWithAssets( + icon: icon, + width: 20.w, + height: 20.h, + iconColor: scheme.iconFg, + fit: BoxFit.contain, + ), + ), + SizedBox(width: 10.w), + title.toText18(isBold: true, weight: FontWeight.w600), + ], + ), + if (status != null) + Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), + decoration: BoxDecoration( + color: scheme.chipBg, + borderRadius: BorderRadius.circular(100.r), + ), + child: status.toText11( + color: scheme.chipFg, + weight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 10.h), + ( + latestDate != null + ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) + : ('Result of --'.needTranslation) + ).toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 12.h), + + valueText.toText28(isBold: true, color: AppColors.textColor, letterSpacing: -2), + + if (args.low != null || args.high != null) ...[ + SizedBox(height: 8.h), + Text( + _referenceText(context), + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ) + ] + ], + ), + ); + } + + String _referenceText(BuildContext context) { + if (args.low != null && args.high != null) { + return 'Reference range: ${args.low} – ${args.high} ${args.unit}'.needTranslation; + } + if (args.low != null) { + return 'Reference range: ≥ ${args.low} ${args.unit}'.needTranslation; + } + if (args.high != null) { + return 'Reference range: ≤ ${args.high} ${args.unit}'.needTranslation; + } + return ''; + } + + Widget _whatIsThisResultCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600), + SizedBox(height: 8.h), + _descriptionText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), + SizedBox(height: 12.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bulb, width: 16.w, height: 16.h, iconColor: AppColors.greyTextColor), + SizedBox(width: 6.w), + Expanded( + child: 'This information is for monitoring and not a diagnosis.'.needTranslation + .toText11(color: AppColors.greyTextColor, weight: FontWeight.w500, maxLine: 3), + ), + ], + ) + ], + ), + ); + } + + Widget _historyCard(BuildContext context, {required List history}) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'History flowchart'.needTranslation.toText16(weight: FontWeight.w600), + Row( + children: [ + // toggle graph/list similar to lab result details + Utils.buildSvgWithAssets( + icon: _isGraphVisible ? AppAssets.graphIcon : AppAssets.listIcon, + width: 18.w, + height: 18.h, + iconColor: AppColors.greyTextColor, + ).onPress(() { + setState(() { + _isGraphVisible = !_isGraphVisible; + }); + }), + SizedBox(width: 10.w), + Utils.buildSvgWithAssets(icon: AppAssets.calendarGrey, width: 18.w, height: 18.h, iconColor: AppColors.greyTextColor), + ], + ), + ], + ), + SizedBox(height: 12.h), + if (history.isEmpty) + Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) + else if (_isGraphVisible) + _buildHistoryGraph(history) + else + _buildHistoryList(context, history), + ], + ), + ); + } + + Widget _buildHistoryGraph(List history) { + final minY = _minY(history); + final maxY = _maxY(history); + return CustomGraph( + dataPoints: history, + makeGraphBasedOnActualValue: true, + leftLabelReservedSize: 40, + showGridLines: true, + leftLabelInterval: _leftInterval(history), + maxY: maxY, + minY: minY, + maxX: history.length.toDouble() - .75, + horizontalInterval: .1, + leftLabelFormatter: (value) { + // Match the lab screen behavior: only show High/Low labels. + final v = double.parse(value.toStringAsFixed(1)); + if (args.high != null && v == args.high) { + return _axisLabel('High'.needTranslation); + } + if (args.low != null && v == args.low) { + return _axisLabel('Low'.needTranslation); + } + return const SizedBox.shrink(); + }, + getDrawingHorizontalLine: (value) { + value = double.parse(value.toStringAsFixed(1)); + if ((args.high != null && value == args.high) || (args.low != null && value == args.low)) { + return FlLine( + color: AppColors.bgGreenColor.withValues(alpha: 0.6), + strokeWidth: 1, + ); + } + return const FlLine(color: Colors.transparent, strokeWidth: 1); + }, + graphColor: AppColors.blackColor, + graphShadowColor: Colors.transparent, + graphGridColor: AppColors.graphGridColor.withValues(alpha: .4), + bottomLabelFormatter: (value, data) { + if (data.isEmpty) return const SizedBox.shrink(); + if (value == 0) return _bottomLabel(data[value.toInt()].label); + if (value == data.length - 1) return _bottomLabel(data[value.toInt()].label); + if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label); + return const SizedBox.shrink(); + }, + rangeAnnotations: _rangeAnnotations(history), + minX: (history.length == 1) ? null : -.2, + scrollDirection: Axis.horizontal, + height: 180.h, + ); + } + + Widget _buildHistoryList(BuildContext context, List history) { + final items = history.reversed.toList(); + final height = items.length < 3 ? items.length * 64.0 : 180.h; + return SizedBox( + height: height, + child: ListView.separated( + padding: EdgeInsets.zero, + itemCount: items.length, + separatorBuilder: (_, __) => Divider( + color: AppColors.borderOnlyColor.withValues(alpha: 0.1), + height: 1, + ), + itemBuilder: (context, index) { + final dp = items[index]; + return Padding( + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + dp.displayTime.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ('${dp.actualValue} ${dp.unitOfMeasurement ?? ''}').toText12( + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + ], + ), + ); + }, + ), + ); + } + + double _minY(List points) { + // IMPORTANT: y-axis uses actual numeric values (from actualValue). + final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + final min = values.reduce((a, b) => a < b ? a : b); + final double boundLow = args.low ?? min; + return (min < boundLow ? min : boundLow) - 1; + } + + double _maxY(List points) { + // IMPORTANT: y-axis uses actual numeric values (from actualValue). + final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + final max = values.reduce((a, b) => a > b ? a : b); + final double boundHigh = args.high ?? max; + return (max > boundHigh ? max : boundHigh) + 1; + } + + double _leftInterval(List points) { + // Keep it stable; graph will mostly show just two labels. + final range = (_maxY(points) - _minY(points)).abs(); + if (range <= 0) return 1; + return (range / 4).clamp(1, 20); + } + + RangeAnnotations? _rangeAnnotations(List points) { + if (args.low == null && args.high == null) return null; + + final minY = _minY(points); + final maxY = _maxY(points); + + final List ranges = []; + + if (args.low != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: minY, + y2: args.low!, + color: AppColors.highAndLow.withValues(alpha: 0.05), + ), + ); + } + + if (args.low != null && args.high != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: args.low!, + y2: args.high!, + color: AppColors.bgGreenColor.withValues(alpha: 0.05), + ), + ); + } + + if (args.high != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: args.high!, + y2: maxY, + color: AppColors.criticalLowAndHigh.withValues(alpha: 0.05), + ), + ); + } + + return RangeAnnotations(horizontalRangeAnnotations: ranges); + } + + List _buildSeries(List vitals, VitalSignDetailsArgs args) { + final List points = []; + + // Build a chronological series (oldest -> newest), skipping null/zero values. + final sorted = List.from(vitals); + sorted.sort((a, b) { + final ad = a.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + final bd = b.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + return ad.compareTo(bd); + }); + + double? metricValue(VitalSignResModel v) { + switch (args.metric) { + case VitalSignMetric.bmi: + return _toDouble(v.bodyMassIndex); + case VitalSignMetric.height: + return _toDouble(v.heightCm); + case VitalSignMetric.weight: + return _toDouble(v.weightKg); + case VitalSignMetric.temperature: + return _toDouble(v.temperatureCelcius); + case VitalSignMetric.heartRate: + return _toDouble(v.heartRate ?? v.pulseBeatPerMinute); + case VitalSignMetric.respiratoryRate: + return _toDouble(v.respirationBeatPerMinute); + case VitalSignMetric.bloodPressure: + // Graph only systolic for now (simple single-series). + return _toDouble(v.bloodPressureHigher); + } + } + + double index = 0; + for (final v in sorted) { + final mv = metricValue(v); + if (mv == null) continue; + if (mv == 0) continue; + + final dt = v.vitalSignDate ?? DateTime.now(); + final label = '${dt.day}/${dt.month}'; + + points.add( + DataPoint( + value: index, + label: label, + actualValue: mv.toStringAsFixed(0), + time: dt, + displayTime: '${dt.day}/${dt.month}/${dt.year}', + unitOfMeasurement: args.unit, + ), + ); + index += 1; + } + + return points; + } + + double? _toDouble(dynamic v) { + if (v == null) return null; + if (v is num) return v.toDouble(); + return double.tryParse(v.toString()); + } + + String _latestValueText(VitalSignResModel? latest) { + if (latest == null) return '--'; + + switch (args.metric) { + case VitalSignMetric.bmi: + final v = _toDouble(latest.bodyMassIndex); + return v == null ? '--' : v.toStringAsFixed(0); + case VitalSignMetric.height: + final v = _toDouble(latest.heightCm); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.weight: + final v = _toDouble(latest.weightKg); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.temperature: + final v = _toDouble(latest.temperatureCelcius); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.heartRate: + final v = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.respiratoryRate: + final v = _toDouble(latest.respirationBeatPerMinute); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.bloodPressure: + final s = _toDouble(latest.bloodPressureHigher); + final d = _toDouble(latest.bloodPressureLower); + if (s == null || d == null) return '--'; + return '${s.toStringAsFixed(0)}/${d.toStringAsFixed(0)}'; + } + } + + String? _statusForLatest(VitalSignResModel? latest) { + if (latest == null) return null; + + switch (args.metric) { + case VitalSignMetric.bmi: + return VitalSignUiModel.bmiStatus(latest.bodyMassIndex); + case VitalSignMetric.bloodPressure: + return VitalSignUiModel.bloodPressureStatus(systolic: latest.bloodPressureHigher, diastolic: latest.bloodPressureLower); + case VitalSignMetric.height: + return null; + case VitalSignMetric.weight: + return latest.weightKg != null ? 'Normal' : null; + case VitalSignMetric.temperature: + return null; + case VitalSignMetric.heartRate: + return (latest.heartRate ?? latest.pulseBeatPerMinute) != null ? 'Normal' : null; + case VitalSignMetric.respiratoryRate: + return latest.respirationBeatPerMinute != null ? 'Normal' : null; + } + } + + String _descriptionText(BuildContext context) { + switch (args.metric) { + case VitalSignMetric.bmi: + return 'BMI is a measurement based on height and weight that estimates body fat.'.needTranslation; + case VitalSignMetric.height: + return 'Height is measured in centimeters and is used to calculate BMI and dosage recommendations.'.needTranslation; + case VitalSignMetric.weight: + return 'Weight helps track overall health, nutrition, and changes over time.'.needTranslation; + case VitalSignMetric.bloodPressure: + return 'Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.'.needTranslation; + case VitalSignMetric.temperature: + return 'Body temperature reflects how hot your body is and may change with infection or inflammation.'.needTranslation; + case VitalSignMetric.heartRate: + return 'Heart rate refers to the number of heart beats per minute.'.needTranslation; + case VitalSignMetric.respiratoryRate: + return 'Respiratory rate is the number of breaths taken per minute.'.needTranslation; + } + } + + String _nextStepsText(BuildContext context) { + switch (args.metric) { + case VitalSignMetric.bmi: + return 'Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.'.needTranslation; + case VitalSignMetric.height: + return 'No action is needed unless your measurement looks incorrect. Update it during your next visit.'.needTranslation; + case VitalSignMetric.weight: + return 'Monitor weight changes. Sudden gain or loss may require medical advice.'.needTranslation; + case VitalSignMetric.bloodPressure: + return 'Keep tracking your blood pressure. High or low readings should be discussed with your doctor.'.needTranslation; + case VitalSignMetric.temperature: + return 'If you have a persistent fever or symptoms, contact your healthcare provider.'.needTranslation; + case VitalSignMetric.heartRate: + return 'Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.'.needTranslation; + case VitalSignMetric.respiratoryRate: + return 'If you notice shortness of breath or abnormal breathing, seek medical advice.'.needTranslation; + } + } + + Widget _nextStepsCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'What should I do next?'.needTranslation.toText16(weight: FontWeight.w600), + SizedBox(height: 8.h), + _nextStepsText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), + ], + ), + ); + } + + Widget _axisLabel(String value) { + return Text( + value, + style: TextStyle( + fontWeight: FontWeight.w600, + fontFamily: 'Poppins', + fontSize: 8.f, + color: AppColors.textColor, + ), + ); + } + + Widget _bottomLabel(String label) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + label, + style: TextStyle( + fontSize: 8.f, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + color: AppColors.labelTextColor, + ), + ), + ); + } +} diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index bd25641..d8b6d7e 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -12,6 +12,8 @@ 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/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart'; +import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_details_page.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class VitalSignPage extends StatefulWidget { @@ -22,6 +24,13 @@ class VitalSignPage extends StatefulWidget { } class _VitalSignPageState extends State { + void _openDetails(VitalSignDetailsArgs args) { + Navigator.of(context).push( + CustomPageRoute( + page: VitalSignDetailsPage(args: args), + ), + ); + } @override void initState() { @@ -65,7 +74,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.bodyMassIndex?.toString() ?? '--', unit: '', status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.bmi, + title: 'BMI', + icon: AppAssets.bmiVital, + unit: '', + ), + ), ), SizedBox(height: 16.h), @@ -76,7 +92,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.heightCm?.toString() ?? '--', unit: 'cm', status: null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.height, + title: 'Height', + icon: AppAssets.heightVital, + unit: 'cm', + ), + ), ), SizedBox(height: 16.h), @@ -87,7 +110,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.weightKg?.toString() ?? '--', unit: 'kg', status: (latestVitalSign?.weightKg != null) ? 'Normal' : null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.weight, + title: 'Weight', + icon: AppAssets.weightVital, + unit: 'kg', + ), + ), ), SizedBox(height: 16.h), @@ -105,7 +135,16 @@ class _VitalSignPageState extends State { systolic: latestVitalSign?.bloodPressureHigher, diastolic: latestVitalSign?.bloodPressureLower, ), - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.bloodPressure, + title: 'Blood Pressure', + icon: AppAssets.bloodPressure, + unit: 'mmHg', + low: 90, + high: 140, + ), + ), ), SizedBox(height: 16.h), @@ -116,7 +155,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.temperatureCelcius?.toString() ?? '--', unit: '°C', status: null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.temperature, + title: 'Temperature', + icon: AppAssets.temperature, + unit: '°C', + low: 36.1, + high: 37.2, + ), + ), ), ], ), @@ -182,7 +230,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.heartRate, + title: 'Heart Rate', + icon: AppAssets.heart, + unit: 'bpm', + low: 60, + high: 100, + ), + ), ), ), ], @@ -197,7 +254,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.respiratoryRate, + title: 'Respiratory rate', + icon: AppAssets.respRate, + unit: 'bpm', + low: 12, + high: 20, + ), + ), ), ], ), From 8ae81d2b248f4508c951e0920f8e2e82fb07a187 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Thu, 8 Jan 2026 10:37:59 +0300 Subject: [PATCH 17/46] fixed parking qr --- lib/core/api_consts.dart | 5 +- lib/core/dependencies.dart | 19 ++ .../models/qr_parking_response_model.dart | 183 ++++++++++++++++++ lib/features/qr_parking/qr_parking_repo.dart | 74 +++++++ .../qr_parking/qr_parking_view_model.dart | 144 ++++++++++++++ .../hmg_services/services_page.dart | 109 ++++------- .../medical_file/medical_file_page.dart | 4 +- lib/presentation/parking/paking_page.dart | 95 +++++---- lib/presentation/parking/parking_slot.dart | 177 +++++++++++++---- lib/routes/app_routes.dart | 14 +- 10 files changed, 670 insertions(+), 154 deletions(-) create mode 100644 lib/features/qr_parking/models/qr_parking_response_model.dart create mode 100644 lib/features/qr_parking/qr_parking_repo.dart create mode 100644 lib/features/qr_parking/qr_parking_view_model.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 66b9b7e..b0fb2a8 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -207,7 +207,7 @@ var GET_APPOINTMENT_DETAILS_BY_NO = 'Services/MobileNotifications.svc/REST/GetAp var NEW_RATE_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate"; var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate"; -var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; +//var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; //URL to get clinic list var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized"; @@ -700,7 +700,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -828,6 +828,7 @@ class ApiConsts { static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; static final String getMonthlyReports = 'Services/Patients.svc/Rest/UpdatePateintHealthSummaryReport'; static final String updatePatientEmail = 'Services/Patients.svc/Rest/UpdatePateintEmail'; + static final String getQrParkingDetails = 'Services/SWP.svc/REST/GetQRParkingByID'; // Ancillary Order Apis static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 555ce29..872fe69 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -41,6 +41,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; @@ -63,6 +64,7 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import '../features/active_prescriptions/active_prescriptions_repo.dart'; @@ -140,6 +142,15 @@ class AppDependencies { getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), ),); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerFactory( + () => QrParkingViewModel( + qrParkingRepo: getIt(), + errorHandlerService: getIt(), + cacheService: getIt(), + ), + ); + // ViewModels // Global/shared VMs → LazySingleton @@ -245,6 +256,14 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); + getIt.registerFactory( + () => QrParkingViewModel( + qrParkingRepo: getIt(), + errorHandlerService: getIt(), + cacheService: getIt(), + ), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( diff --git a/lib/features/qr_parking/models/qr_parking_response_model.dart b/lib/features/qr_parking/models/qr_parking_response_model.dart new file mode 100644 index 0000000..2e90da1 --- /dev/null +++ b/lib/features/qr_parking/models/qr_parking_response_model.dart @@ -0,0 +1,183 @@ + + +class QrParkingResponseModel { + dynamic totalRecords; + dynamic nRowID; + int? qRParkingID; + String? description; + String? descriptionN; + dynamic qRCompare; + dynamic qRValue; + String? imagePath; + bool? isActive; + int? parkingID; + int? branchID; + int? companyID; + int? buildingID; + int? rowID; + int? gateID; + int? floorID; + dynamic imagePath1; + int? createdBy; + String? createdOn; + dynamic editedBy; + dynamic editedOn; + String? parkingDescription; + String? parkingDescriptionN; + String? gateDescription; + String? gateDescriptionN; + String? branchDescription; + String? branchDescriptionN; + String? companyDescription; + String? companyDescriptionN; + String? rowDescription; + String? rowDescriptionN; + String? floorDescription; + String? floorDescriptionN; + String? buildingDescription; + String? buildingDescriptionN; + String? qRParkingCode; + String? parkingCode; + double? latitude; + double? longitude; + String? qRImageStr; + + QrParkingResponseModel({ + this.totalRecords, + this.nRowID, + this.qRParkingID, + this.description, + this.descriptionN, + this.qRCompare, + this.qRValue, + this.imagePath, + this.isActive, + this.parkingID, + this.branchID, + this.companyID, + this.buildingID, + this.rowID, + this.gateID, + this.floorID, + this.imagePath1, + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.parkingDescription, + this.parkingDescriptionN, + this.gateDescription, + this.gateDescriptionN, + this.branchDescription, + this.branchDescriptionN, + this.companyDescription, + this.companyDescriptionN, + this.rowDescription, + this.rowDescriptionN, + this.floorDescription, + this.floorDescriptionN, + this.buildingDescription, + this.buildingDescriptionN, + this.qRParkingCode, + this.parkingCode, + this.latitude, + this.longitude, + this.qRImageStr, + }); + + QrParkingResponseModel.fromJson(Map json) { + totalRecords = json['TotalRecords']; + nRowID = json['nRowID']; + qRParkingID = json['QRParkingID']; + description = json['Description']; + descriptionN = json['DescriptionN']; + qRCompare = json['QRCompare']; + qRValue = json['QRValue']; + imagePath = json['ImagePath']; + isActive = json['IsActive']; + parkingID = json['ParkingID']; + branchID = json['BranchID']; + companyID = json['CompanyID']; + buildingID = json['BuildingID']; + rowID = json['RowID']; + gateID = json['GateID']; + floorID = json['FloorID']; + imagePath1 = json['ImagePath1']; + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + parkingDescription = json['ParkingDescription']; + parkingDescriptionN = json['ParkingDescriptionN']; + gateDescription = json['GateDescription']; + gateDescriptionN = json['GateDescriptionN']; + branchDescription = json['BranchDescription']; + branchDescriptionN = json['BranchDescriptionN']; + companyDescription = json['CompanyDescription']; + companyDescriptionN = json['CompanyDescriptionN']; + rowDescription = json['RowDescription']; + rowDescriptionN = json['RowDescriptionN']; + floorDescription = json['FloorDescription']; + floorDescriptionN = json['FloorDescriptionN']; + buildingDescription = json['BuildingDescription']; + buildingDescriptionN = json['BuildingDescriptionN']; + qRParkingCode = json['QRParkingCode']; + parkingCode = json['ParkingCode']; + latitude = _toDouble(json['Latitude']); + longitude = _toDouble(json['Longitude']); + qRImageStr = json['QRImageStr']; + } + + Map toJson() { + final Map data = {}; + data['TotalRecords'] = totalRecords; + data['nRowID'] = nRowID; + data['QRParkingID'] = qRParkingID; + data['Description'] = description; + data['DescriptionN'] = descriptionN; + data['QRCompare'] = qRCompare; + data['QRValue'] = qRValue; + data['ImagePath'] = imagePath; + data['IsActive'] = isActive; + data['ParkingID'] = parkingID; + data['BranchID'] = branchID; + data['CompanyID'] = companyID; + data['BuildingID'] = buildingID; + data['RowID'] = rowID; + data['GateID'] = gateID; + data['FloorID'] = floorID; + data['ImagePath1'] = imagePath1; + data['CreatedBy'] = createdBy; + data['CreatedOn'] = createdOn; + data['EditedBy'] = editedBy; + data['EditedOn'] = editedOn; + data['ParkingDescription'] = parkingDescription; + data['ParkingDescriptionN'] = parkingDescriptionN; + data['GateDescription'] = gateDescription; + data['GateDescriptionN'] = gateDescriptionN; + data['BranchDescription'] = branchDescription; + data['BranchDescriptionN'] = branchDescriptionN; + data['CompanyDescription'] = companyDescription; + data['CompanyDescriptionN'] = companyDescriptionN; + data['RowDescription'] = rowDescription; + data['RowDescriptionN'] = rowDescriptionN; + data['FloorDescription'] = floorDescription; + data['FloorDescriptionN'] = floorDescriptionN; + data['BuildingDescription'] = buildingDescription; + data['BuildingDescriptionN'] = buildingDescriptionN; + data['QRParkingCode'] = qRParkingCode; + data['ParkingCode'] = parkingCode; + data['Latitude'] = latitude; + data['Longitude'] = longitude; + data['QRImageStr'] = qRImageStr; + return data; + } + + static double? _toDouble(dynamic v) { + if (v == null) return null; + if (v is double) return v; + if (v is int) return v.toDouble(); + return double.tryParse(v.toString()); + } +} + diff --git a/lib/features/qr_parking/qr_parking_repo.dart b/lib/features/qr_parking/qr_parking_repo.dart new file mode 100644 index 0000000..1ec905f --- /dev/null +++ b/lib/features/qr_parking/qr_parking_repo.dart @@ -0,0 +1,74 @@ + + +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart'; +import '../../core/api/api_client.dart'; +import '../../core/api_consts.dart'; +import '../../core/common_models/generic_api_model.dart'; +import '../../core/exceptions/api_failure.dart'; +import '../../services/logger_service.dart'; + + +abstract class QrParkingRepo { + Future>>> + getQrParking({ + required int qrParkingId, + }); +} + +class QrParkingRepoImp implements QrParkingRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + QrParkingRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future>>> + getQrParking({required int qrParkingId}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getQrParkingDetails, // GetQRParkingByID + body: {'QRParkingID': qrParkingId}, + onFailure: (error, statusCode, + {messageStatus, failureType}) { + failure = failureType ?? + StatusCodeFailure("$error ($statusCode)"); + }, + onSuccess: (response, statusCode, + {messageStatus, errorMessage}) { + final list = + (response['List_SWP_QRParkingModel'] as List?) ?? []; + + final res = list + .map((e) => QrParkingResponseModel.fromJson( + Map.from(e), + )) + .toList(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: res, + ); + }, + ); + + 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/qr_parking/qr_parking_view_model.dart b/lib/features/qr_parking/qr_parking_view_model.dart new file mode 100644 index 0000000..b0b688f --- /dev/null +++ b/lib/features/qr_parking/qr_parking_view_model.dart @@ -0,0 +1,144 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; + +import '../../services/cache_service.dart'; +import '../../services/error_handler_service.dart'; +import 'models/qr_parking_response_model.dart'; + + +class QrParkingViewModel extends ChangeNotifier { + final QrParkingRepo qrParkingRepo; + final ErrorHandlerService errorHandlerService; + final CacheService cacheService; + String IS_GO_TO_PARKING = 'IS_GO_TO_PARKING'; + + bool isLoading = false; + String? error; + + bool isSavePark = false; + QrParkingResponseModel? qrParkingModel; + List qrParkingList = []; + + QrParkingViewModel({ + required this.qrParkingRepo, + required this.errorHandlerService, + required this.cacheService, + }); + + + Future scanAndGetParking() async { + try { + error = null; + isLoading = true; + notifyListeners(); + + final result = await BarcodeScanner.scan(); + + if (result.type != ResultType.Barcode) { + isLoading = false; + notifyListeners(); + return null; + } + + final raw = result.rawContent.trim(); + if (raw.isEmpty) { + error = "Invalid QR Code"; + isLoading = false; + notifyListeners(); + return null; + } + + final qrParkingId = _extractQrParkingId(raw); + if (qrParkingId == null) { + error = "Invalid QR Code"; + isLoading = false; + notifyListeners(); + return null; + } + + final apiResult = + await qrParkingRepo.getQrParking(qrParkingId: qrParkingId); + + final model = apiResult.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + error = failure.toString(); + return null; + }, + (apiResponse) { + qrParkingList = apiResponse.data ?? []; + if (qrParkingList.isNotEmpty) { + return qrParkingList.first; + } + error = "Invalid Qr Code"; + return null; + }, + ); + + if (model != null) { + qrParkingModel = model; + isSavePark = true; + + await cacheService.saveObject( + key: IS_GO_TO_PARKING, + value: model.toJson(), + ); + } + + isLoading = false; + notifyListeners(); + return model; + } catch (e) { + error = "Scan error"; + isLoading = false; + notifyListeners(); + return null; + } + } + + /// Load saved parking + Future getIsSaveParking() async { + isLoading = true; + notifyListeners(); + + final parking = + await cacheService.getObject(key: IS_GO_TO_PARKING); + + if (parking != null) { + isSavePark = true; + qrParkingModel = QrParkingResponseModel.fromJson( + Map.from(parking), + ); + } else { + isSavePark = false; + qrParkingModel = null; + } + + isLoading = false; + notifyListeners(); + } + + /// Reset parking + Future clearParking() async { + await cacheService.remove(key: IS_GO_TO_PARKING); + isSavePark = false; + qrParkingModel = null; + notifyListeners(); + } + + int? _extractQrParkingId(String raw) { + try { + if (raw.startsWith("{")) { + final data = jsonDecode(raw); + return int.tryParse(data['QRParkingID'].toString()); + } + return int.tryParse(raw); + } catch (_) { + return null; + } + } +} + + diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 87a0e0e..a3d5d76 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -21,7 +21,7 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; - +import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -31,10 +31,9 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import '../../core/dependencies.dart' show getIt; +import '../../features/qr_parking/qr_parking_view_model.dart'; class ServicesPage extends StatelessWidget { ServicesPage({super.key}); @@ -89,30 +88,18 @@ class ServicesPage extends StatelessWidget { route: null, onTap: () async { LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { - // await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { + await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { LoaderBottomSheet.hideLoader(); Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( CustomPageRoute( page: BloodDonationPage(), ), ); - // }, onError: (err) { - // LoaderBottomSheet.hideLoader(); - // }); + }); }, onError: (err) { LoaderBottomSheet.hideLoader(); }); }), - HmgServicesComponentModel( - 11, - "Covid 19 Test".needTranslation, - "".needTranslation, - AppAssets.covid19icon, - bgColor: AppColors.covid29Color, - true, - route: AppRoutes.covid19Test, - ) - // HmgServicesComponentModel( // 3, // "Home Health Care".needTranslation, @@ -176,30 +163,8 @@ class ServicesPage extends StatelessWidget { route: AppRoutes.smartWatches, // route: AppRoutes.huaweiHealthExample, ), - // HmgServicesComponentModel( - // 12, - // "Latest News".needTranslation, - // "".needTranslation, - // AppAssets.news, - // true, - // bgColor: AppColors.bgGreenColor, - // textColor: AppColors.blackColor, - // route: "https://twitter.com/HMG", - // isExternalLink: true, - // ), - HmgServicesComponentModel( - 12, - "Monthly Reports".needTranslation, - "".needTranslation, - AppAssets.report_icon, - true, - bgColor: AppColors.bgGreenColor, - textColor: AppColors.blackColor, - route: AppRoutes.monthlyReports, - ), ]; - @override Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); @@ -467,43 +432,37 @@ class ServicesPage extends StatelessWidget { ), SizedBox(width: 16.w), Expanded( - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ParkingPage(), - ), - ); - }, - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.car_parking_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) - ], - ).onPress(() { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ParkingPage(), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.car_parking_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) + ], + ).onPress(() { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), ), - ); - }), - ), + ), + ); + + }), ), ), ), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 52dc9ff..80a45ac 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1069,7 +1069,9 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.monthly_reports_icon, isLargeText: true, iconSize: 36.h, - ), + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.monthlyReports); + }), MedicalFileCard( label: "Medical Reports".needTranslation, textColor: AppColors.blackColor, diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index ce9b6ab..cd1e8bc 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -1,16 +1,18 @@ - import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_export.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/presentation/parking/parking_slot.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:provider/provider.dart'; +import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/routes/custom_page_route.dart'; + class ParkingPage extends StatefulWidget { const ParkingPage({super.key}); @@ -19,10 +21,29 @@ class ParkingPage extends StatefulWidget { } class _ParkingPageState extends State { - String? scannedCode; + Future _readQR(BuildContext context) async { + final vm = context.read(); + + final model = await vm.scanAndGetParking(); + + if (model == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(vm.error ?? "Invalid Qr Code")), + ); + return; + } + + Navigator.of(context).push( + CustomPageRoute( + page: ParkingSlot(model: model), + ), + ); + } @override Widget build(BuildContext context) { + final vm = context.watch(); // عشان loading + return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( @@ -38,22 +59,24 @@ class _ParkingPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Parking".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 27.f, - fontWeight: FontWeight.w600)), - Container( - decoration: RoundedRectangleBorder() - .toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - // margin: EdgeInsets.all(10), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Text( + Text( + "Parking".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 27.f, + fontWeight: FontWeight.w600, + ), + ), + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Text( "Dr. Sulaiman Al Habib hospital are conduction a test for the emerging corona" " virus and issuing travel certificates 24/7 in a short time and with high accuracy." " Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches " @@ -62,19 +85,20 @@ class _ParkingPageState extends State { "Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes.", style: TextStyle( color: AppColors.textColor, - fontSize: 12, height: 1.4, fontWeight: FontWeight.w500), - ), - ), - ).paddingOnly( top: 16, bottom: 16), - + fontSize: 12, + height: 1.4, + fontWeight: FontWeight.w500, + ), + ), + ), + ).paddingOnly(top: 16, bottom: 16), ], ), ), ), /// Bottom button - Container - ( + Container( decoration: RoundedRectangleBorder() .toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -93,13 +117,17 @@ class _ParkingPageState extends State { borderRadius: BorderRadius.circular(10), ), ), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: ParkingSlot(), - ) ); - }, - child: Text( + onPressed: vm.isLoading ? null : () => _readQR(context), + child: vm.isLoading + ? const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Text( "Read Barcodes", style: TextStyle( fontSize: 18, @@ -116,3 +144,4 @@ class _ParkingPageState extends State { ); } } + diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 094dcb7..013bb6f 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -1,26 +1,106 @@ - - import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart'; +import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:provider/provider.dart'; + class ParkingSlot extends StatefulWidget { - const ParkingSlot({super.key}); + final QrParkingResponseModel model; + + const ParkingSlot({ + super.key, + required this.model, + }); @override State createState() => _ParkingSlotState(); } class _ParkingSlotState extends State { - String? scannedCode; + + void _openDirection() { + final lat = widget.model.latitude; + final lng = widget.model.longitude; + + final valid = lat != null && + lng != null && + !(lat == 0.0 && lng == 0.0) && + lat >= -90 && lat <= 90 && + lng >= -180 && lng <= 180; + + if (!valid) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Parking location not available")), + ); + return; + } + + MapsLauncher.launchCoordinates(lat, lng); + } + + Future _resetDirection() async { + final vm = context.read(); + await vm.clearParking(); + Navigator.of(context).popUntil((route) => route.isFirst); + } + + DateTime? _parseDotNetDate(String? value) { + if (value == null || value.isEmpty) return null; + + final regExp = RegExp(r'Date\((\d+)([+-]\d+)?\)'); + final match = regExp.firstMatch(value); + if (match == null) return null; + + final milliseconds = int.tryParse(match.group(1)!); + if (milliseconds == null) return null; + + return DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true) + .toLocal(); + } + + + String _formatPrettyDate(String? value) { + final date = _parseDotNetDate(value); + if (date == null) return '-'; + + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ]; + + final day = date.day; + final month = months[date.month - 1]; + final year = date.year; + + return "$day $month $year"; + } + + + String _formatPrettyTime(String? value) { + final date = _parseDotNetDate(value); + if (date == null) return '-'; + + int hour = date.hour; + final minute = date.minute.toString().padLeft(2, '0'); + + final isPM = hour >= 12; + final period = isPM ? 'PM' : 'AM'; + + hour = hour % 12; + if (hour == 0) hour = 12; + + return "${hour.toString().padLeft(2, '0')}:$minute $period"; + } @override Widget build(BuildContext context) { @@ -34,7 +114,7 @@ class _ParkingSlotState extends State { body: LayoutBuilder( builder: (context, constraints) { final maxW = constraints.maxWidth; - final contentW = maxW > 600 ? 600.0 : maxW; // حد أقصى للتابلت + final contentW = maxW > 600 ? 600.0 : maxW; return Align( alignment: Alignment.topCenter, @@ -45,9 +125,11 @@ class _ParkingSlotState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Container( width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, @@ -58,7 +140,7 @@ class _ParkingSlotState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Parking Slot Details", + "Parking Slot Details".needTranslation, style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -70,10 +152,26 @@ class _ParkingSlotState extends State { spacing: 4, runSpacing: 4, children: [ - AppCustomChipWidget(labelText: "Slot: B-24".needTranslation), - AppCustomChipWidget(labelText: "Basement: Zone B".needTranslation), - AppCustomChipWidget(labelText: "Date: 16 Dec 2025".needTranslation), - AppCustomChipWidget(labelText: "Parked Since: 10:32 AM".needTranslation), + AppCustomChipWidget( + labelText: + "Slot: ${widget.model.qRParkingCode ?? '-'}" + .needTranslation, + ), + AppCustomChipWidget( + labelText: + "Basement: ${widget.model.floorDescription ?? '-'}" + .needTranslation, + ), + AppCustomChipWidget( + labelText: + "Date: ${_formatPrettyDate(widget.model.createdOn)}" + .needTranslation, + ), + AppCustomChipWidget( + labelText: + "Parked Since: ${_formatPrettyTime(widget.model.createdOn)}" + .needTranslation, + ), ], ), ], @@ -93,42 +191,40 @@ class _ParkingSlotState extends State { borderRadius: BorderRadius.circular(10), ), ), - onPressed: () {}, + onPressed: _openDirection, child: Text( - "Get Direction", + "Get Direction".needTranslation, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.whiteColor, ), ), ), ), - const Spacer(), - SizedBox( - width: double.infinity, - height: 48.h, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - side: BorderSide(color: AppColors.primaryRedColor), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: () { - // Reset direction logic - }, - child: Text( - "Reset Direction", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.primaryRedColor, - ), - ), - ), - ), + // const Spacer(), + // SizedBox( + // width: double.infinity, + // height: 48.h, + // child: OutlinedButton( + // style: OutlinedButton.styleFrom( + // side: BorderSide(color: AppColors.primaryRedColor), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // ), + // onPressed: _resetDirection, + // child: Text( + // "Reset Direction".needTranslation, + // style: TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w600, + // color: AppColors.primaryRedColor, + // ), + // ), + // ), + // ), ], ), ), @@ -136,7 +232,8 @@ class _ParkingSlotState extends State { ); }, ), - ); } } + + diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index dd38180..5b18bce 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -24,12 +24,14 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_sele import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; +import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/covid19test/covid19_landing_page.dart'; import '../core/dependencies.dart'; import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../presentation/monthly_reports/monthly_reports_page.dart'; +import '../presentation/parking/paking_page.dart'; import '../services/error_handler_service.dart'; import 'package:provider/provider.dart'; @@ -65,6 +67,7 @@ class AppRoutes { static const String userInfoSelection = '/userInfoSelection'; static const String userInfoFlowManager = '/userInfoFlowManager'; static const String monthlyReports = '/monthlyReportsPage'; + static const String qrParking = '/qrParkingPage'; static Map get routes => { initialRoute: (context) => SplashPage(), @@ -92,7 +95,6 @@ class AppRoutes { covid19Test: (context) => Covid19LandingPage(), // // healthCalculatorsPage: (context) => HealthCalculatorsPage(), - // monthlyReports: (context) => MonthlyReportsPage() monthlyReports: (context) => ChangeNotifierProvider( create: (_) => MonthlyReportsViewModel( monthlyReportsRepo: getIt(), @@ -102,6 +104,12 @@ class AppRoutes { ), healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator), - healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter) - }; + healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter), + qrParking: (context) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), + ), + + + }; } From 02930a901e195b9655bf6ed6748f3bff5b80fcd2 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 8 Jan 2026 12:52:03 +0300 Subject: [PATCH 18/46] Indoor navigation implementation & Landing page swiper card updates --- .../com/ejada/hmg/penguin/PenguinNavigator.kt | 2 +- assets/images/svg/back_top_nav_icon.svg | 4 + assets/images/svg/forward_top_nav_icon.svg | 4 + ios/Runner/Penguin/PenguinNavigator.swift | 2 +- ios/Runner/Penguin/PenguinView.swift | 32 +- lib/core/api_consts.dart | 7 +- lib/core/app_assets.dart | 4 +- lib/core/dependencies.dart | 5 + lib/core/utils/utils.dart | 10 +- .../hospital_selection_view_model.dart | 7 +- .../notification_response_model.dart | 96 +++ .../notifications/notifications_repo.dart | 78 +++ .../notifications_view_model.dart | 113 +++ .../todo_section/todo_section_repo.dart | 27 + .../todo_section/todo_section_view_model.dart | 28 +- lib/main.dart | 4 + .../home/data/landing_page_data.dart | 4 +- lib/presentation/home/landing_page.dart | 655 +++++++++++------- .../home/widgets/small_service_card.dart | 25 + .../notifications_list_page.dart | 72 ++ .../ancillary_order_payment_page.dart | 6 +- lib/widgets/appbar/collapsing_list_view.dart | 2 +- 22 files changed, 906 insertions(+), 281 deletions(-) create mode 100644 assets/images/svg/back_top_nav_icon.svg create mode 100644 assets/images/svg/forward_top_nav_icon.svg create mode 100644 lib/features/notifications/models/resp_models/notification_response_model.dart create mode 100644 lib/features/notifications/notifications_repo.dart create mode 100644 lib/features/notifications/notifications_view_model.dart create mode 100644 lib/presentation/notifications/notifications_list_page.dart diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt index b822d67..70889d3 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt @@ -21,7 +21,7 @@ class PenguinNavigator() { val postToken = PostToken(clientID, clientKey) getToken(mContext, postToken, object : RefIdDelegate { override fun onRefByIDSuccess(PoiId: String?) { - Log.e("navigateTo", "PoiId is+++++++ $PoiId") + Log.e("navigateTo", "PoiId is+++++++ $refID") PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate { override fun onRefByIDSuccess(PoiId: String?) { diff --git a/assets/images/svg/back_top_nav_icon.svg b/assets/images/svg/back_top_nav_icon.svg new file mode 100644 index 0000000..62bf96b --- /dev/null +++ b/assets/images/svg/back_top_nav_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/forward_top_nav_icon.svg b/assets/images/svg/forward_top_nav_icon.svg new file mode 100644 index 0000000..089161c --- /dev/null +++ b/assets/images/svg/forward_top_nav_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios/Runner/Penguin/PenguinNavigator.swift b/ios/Runner/Penguin/PenguinNavigator.swift index 31cf626..5b9934b 100644 --- a/ios/Runner/Penguin/PenguinNavigator.swift +++ b/ios/Runner/Penguin/PenguinNavigator.swift @@ -14,7 +14,7 @@ class PenguinNavigator { } func navigateToPOI( referenceId:String,completion: @escaping (Bool, String?) -> Void) { - PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: true) { [weak self] token, error in + PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: false) { [weak self] token, error in if let error = error { let errorMessage = "Token error while getting the for Navigate to method" diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift index d5303e2..508fb74 100644 --- a/ios/Runner/Penguin/PenguinView.swift +++ b/ios/Runner/Penguin/PenguinView.swift @@ -312,6 +312,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia print("====== after eventsDelegate onPenNavSuccess =========") + + PenNavUIManager.shared.navigate(to: "108") @@ -385,21 +387,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia - self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in - - if success { - - print("Navigation successful") - - } else { - - print("Navigation failed: \(errorMessage ?? "Unknown error")") - - } - - - - } +// self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in +// +// if success { +// +// print("Navigation successful") +// +// } else { +// +// print("Navigation failed: \(errorMessage ?? "Unknown error")") +// +// } +// +// +// +// } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 1846d1e..edce236 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -398,6 +398,9 @@ var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_Get var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord'; var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions'; +///Push Notifications +var GET_ALL_NOTIFICATIONS = 'Services/MobileNotifications.svc/REST/PushNotification_GetAllNotifications'; + ///My Trackers var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; @@ -672,10 +675,12 @@ const FAMILY_FILES = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStat var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatbot_SendMedicationInstructionByWhatsApp'; +const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; + class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index a581e78..3555e62 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -218,11 +218,13 @@ class AppAssets { static const String activity = '$svgBasePath/activity.svg'; static const String age = '$svgBasePath/age_icon.svg'; static const String gender = '$svgBasePath/gender_icon.svg'; - static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg'; static const String trade_down_red = '$svgBasePath/trade_down_red.svg'; static const String pharmacy_icon = '$svgBasePath/phramacy_icon.svg'; + static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg'; + static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index ebf0a84..43e33c4 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -38,6 +38,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_rep import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; @@ -144,6 +146,7 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -281,5 +284,7 @@ class AppDependencies { getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); + + getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 03ff6b5..e8fa650 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -43,7 +43,7 @@ class Utils { { "Desciption": "Sahafa Hospital", "DesciptionN": "مستشفى الصحافة", - "ID": 1, + "ID": 1, // Campus ID "LegalName": "Sahafa Hospital", "LegalNameN": "مستشفى الصحافة", "Name": "Sahafa Hospital", @@ -62,12 +62,12 @@ class Utils { "UsingInDoctorApp": false },{ "Desciption": "Jeddah Hospital", - "DesciptionN": "مستشفى الصحافة", - "ID": 3, + "DesciptionN": "مستشفى جدة", + "ID": 3, // Campus ID "LegalName": "Jeddah Hospital", - "LegalNameN": "مستشفى الصحافة", + "LegalNameN": "مستشفى جدة", "Name": "Jeddah Hospital", - "NameN": "مستشفى الصحافة", + "NameN": "مستشفى جدة", "PhoneNumber": "+966115222222", "SetupID": "013311", "DistanceInKilometers": 0, diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index dd9531f..59c881c 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -79,13 +79,14 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { } - void openPenguin(HospitalsModel hospital) { - initPenguinSDK(hospital.iD); + void openPenguin(HospitalsModel hospital, {String clinicID = ""}) { + initPenguinSDK(hospital.iD, clinicID: clinicID); } - initPenguinSDK(int projectID) async { + initPenguinSDK(int projectID, {String clinicID = ""}) async { NavigationClinicDetails data = NavigationClinicDetails(); data.projectId = projectID.toString(); + data.clinicId = clinicID; final bool permited = await AppPermission.askPenguinPermissions(); if (!permited) { Map statuses = await [ diff --git a/lib/features/notifications/models/resp_models/notification_response_model.dart b/lib/features/notifications/models/resp_models/notification_response_model.dart new file mode 100644 index 0000000..034e19c --- /dev/null +++ b/lib/features/notifications/models/resp_models/notification_response_model.dart @@ -0,0 +1,96 @@ +class NotificationResponseModel { + int? id; + int? recordId; + int? patientID; + bool? projectOutSA; + String? deviceType; + String? deviceToken; + String? message; + String? messageType; + String? messageTypeData; + String? videoURL; + bool? isQueue; + String? isQueueOn; + String? createdOn; + dynamic createdBy; + String? notificationType; + bool? isSent; + String? isSentOn; + bool? isRead; + dynamic isReadOn; + int? channelID; + int? projectID; + + NotificationResponseModel( + {this.id, + this.recordId, + this.patientID, + this.projectOutSA, + this.deviceType, + this.deviceToken, + this.message, + this.messageType, + this.messageTypeData, + this.videoURL, + this.isQueue, + this.isQueueOn, + this.createdOn, + this.createdBy, + this.notificationType, + this.isSent, + this.isSentOn, + this.isRead, + this.isReadOn, + this.channelID, + this.projectID}); + + NotificationResponseModel.fromJson(Map json) { + id = json['Id']; + recordId = json['RecordId']; + patientID = json['PatientID']; + projectOutSA = json['ProjectOutSA']; + deviceType = json['DeviceType']; + deviceToken = json['DeviceToken']; + message = json['Message']; + messageType = json['MessageType']; + messageTypeData = json['MessageTypeData']; + videoURL = json['VideoURL']; + isQueue = json['IsQueue']; + isQueueOn = json['IsQueueOn']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + notificationType = json['NotificationType']; + isSent = json['IsSent']; + isSentOn = json['IsSentOn']; + isRead = json['IsRead']; + isReadOn = json['IsReadOn']; + channelID = json['ChannelID']; + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['Id'] = this.id; + data['RecordId'] = this.recordId; + data['PatientID'] = this.patientID; + data['ProjectOutSA'] = this.projectOutSA; + data['DeviceType'] = this.deviceType; + data['DeviceToken'] = this.deviceToken; + data['Message'] = this.message; + data['MessageType'] = this.messageType; + data['MessageTypeData'] = this.messageTypeData; + data['VideoURL'] = this.videoURL; + data['IsQueue'] = this.isQueue; + data['IsQueueOn'] = this.isQueueOn; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['NotificationType'] = this.notificationType; + data['IsSent'] = this.isSent; + data['IsSentOn'] = this.isSentOn; + data['IsRead'] = this.isRead; + data['IsReadOn'] = this.isReadOn; + data['ChannelID'] = this.channelID; + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/features/notifications/notifications_repo.dart b/lib/features/notifications/notifications_repo.dart new file mode 100644 index 0000000..853a979 --- /dev/null +++ b/lib/features/notifications/notifications_repo.dart @@ -0,0 +1,78 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class NotificationsRepo { + Future>>> getAllNotifications({ + required int notificationStatusID, + required int pagingSize, + required int currentPage, + }); +} + +class NotificationsRepoImp implements NotificationsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + NotificationsRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getAllNotifications({ + required int notificationStatusID, + required int pagingSize, + required int currentPage, + }) async { + Map mapDevice = { + "NotificationStatusID": notificationStatusID, + "pagingSize": pagingSize, + "currentPage": currentPage, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_ALL_NOTIFICATIONS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_GetAllNotificationsFromPool']; + if (list == null || list.isEmpty) { + // Return empty list if no notifications + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: [], + ); + return; + } + + final notifications = list.map((item) => NotificationResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: notifications, + ); + } 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/notifications/notifications_view_model.dart b/lib/features/notifications/notifications_view_model.dart new file mode 100644 index 0000000..e15e267 --- /dev/null +++ b/lib/features/notifications/notifications_view_model.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class NotificationsViewModel extends ChangeNotifier { + bool isNotificationsLoading = false; + bool hasMoreNotifications = true; + + NotificationsRepo notificationsRepo; + ErrorHandlerService errorHandlerService; + + List notificationsList = []; + + int currentPage = 0; + int pagingSize = 14; + int notificationStatusID = 2; // Default to status 2 (e.g., unread) + + NotificationsViewModel({ + required this.notificationsRepo, + required this.errorHandlerService, + }); + + initNotificationsViewModel({int? statusID}) { + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + isNotificationsLoading = true; + // if (statusID != null) { + // notificationStatusID = statusID; + // } + // getAllNotifications(); + notifyListeners(); + } + + setNotificationStatusID(int statusID) { + notificationStatusID = statusID; + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + notifyListeners(); + } + + Future getAllNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isNotificationsLoading = true; + notifyListeners(); + + final result = await notificationsRepo.getAllNotifications( + notificationStatusID: notificationStatusID, + pagingSize: pagingSize, + currentPage: currentPage, + ); + + result.fold( + (failure) async { + isNotificationsLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isNotificationsLoading = false; + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } + } else if (apiResponse.messageStatus == 1) { + final newNotifications = apiResponse.data ?? []; + + if (newNotifications.isEmpty || newNotifications.length < pagingSize) { + hasMoreNotifications = false; + } + + notificationsList.addAll(newNotifications); + currentPage++; + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future refreshNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + await getAllNotifications(onSuccess: onSuccess, onError: onError); + } + + Future loadMoreNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + await getAllNotifications(onSuccess: onSuccess, onError: onError); + } + + @override + void dispose() { + super.dispose(); + } +} diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart index 008b22c..76a4438 100644 --- a/lib/features/todo_section/todo_section_repo.dart +++ b/lib/features/todo_section/todo_section_repo.dart @@ -48,6 +48,8 @@ abstract class TodoSectionRepo { }); Future> applePayInsertRequest({required dynamic applePayInsertRequest}); + + Future> getPatientDashboard(); } class TodoSectionRepoImp implements TodoSectionRepo { @@ -376,4 +378,29 @@ class TodoSectionRepoImp implements TodoSectionRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getPatientDashboard() async { + Map mapDevice = {}; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + DASHBOARD, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in applePayInsertRequest: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index c0fb96b..92d893a 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -10,11 +10,15 @@ class TodoSectionViewModel extends ChangeNotifier { TodoSectionViewModel({required this.todoSectionRepo, required this.errorHandlerService}); + String? notificationsCount = "0"; + initializeTodoSectionViewModel() async { patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; isAncillaryDetailsProceduresLoading = true; - await getPatientOnlineAncillaryOrderList(); + notificationsCount = "0"; + getPatientOnlineAncillaryOrderList(); + getPatientDashboard(); } bool isAncillaryOrdersLoading = false; @@ -28,6 +32,28 @@ class TodoSectionViewModel extends ChangeNotifier { notifyListeners(); } + Future getPatientDashboard({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await todoSectionRepo.getPatientDashboard(); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + // if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + // } else if (apiResponse.messageStatus == 1) { + notificationsCount = + apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + // } + }, + ); + } + Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; diff --git a/lib/main.dart b/lib/main.dart index eb57c37..0224afa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,6 +27,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; @@ -177,6 +178,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index c693479..ef2832b 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -46,7 +46,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "health_converters", - icon: AppAssets.health_calculators_icon, + icon: AppAssets.health_converters_icon, title: "Health", subtitle: "Converters", backgroundColor: AppColors.whiteColor, @@ -56,7 +56,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "parking_guide", - icon: AppAssets.health_calculators_icon, + icon: AppAssets.car_parking_icon, title: "Parking", subtitle: "Guide", backgroundColor: AppColors.whiteColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b25ae82..a0dd27f 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -21,11 +21,13 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_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'; @@ -44,6 +46,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/notifications/notifications_list_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; @@ -59,6 +62,8 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:provider/provider.dart'; +import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; + class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -80,6 +85,7 @@ class _LandingPageState extends State { late BookAppointmentsViewModel bookAppointmentsViewModel; late EmergencyServicesViewModel emergencyServicesViewModel; late TodoSectionViewModel todoSectionViewModel; + late NotificationsViewModel notificationsViewModel; final SwiperController _controller = SwiperController(); @@ -106,22 +112,26 @@ class _LandingPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); emergencyServicesViewModel.checkPatientERAdvanceBalance(); myAppointmentsViewModel.getPatientAppointmentQueueDetails(); - if(!appState.isRatedVisible) { - appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { - if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, - onSuccess: ((response) { - appointmentRatingViewModel.setClinicOrDoctor(false); - appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation); - appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation); - openLastRating(); - appState.setRatedVisible(true); - }), - ); - } - }, - ); - } + notificationsViewModel.initNotificationsViewModel(); + + // Commented as per new requirement to remove rating popup from the app + + // if(!appState.isRatedVisible) { + // appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { + // if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + // appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, + // onSuccess: ((response) { + // appointmentRatingViewModel.setClinicOrDoctor(false); + // appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation); + // appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation); + // openLastRating(); + // appState.setRatedVisible(true); + // }), + // ); + // } + // }, + // ); + // } } }); super.initState(); @@ -135,6 +145,7 @@ class _LandingPageState extends State { immediateLiveCareViewModel = Provider.of(context, listen: false); emergencyServicesViewModel = Provider.of(context, listen: false); todoSectionViewModel = Provider.of(context, listen: false); + notificationsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return PopScope( canPop: false, @@ -180,37 +191,65 @@ class _LandingPageState extends State { padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, ), - Row( - mainAxisSize: MainAxisSize.min, - spacing: 12.h, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }), - Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: MedicalFilePage(), - // // page: LoginScreen(), - // ), - // ); - }), - Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, - ); - }), - ], - ), + Consumer(builder: (context, todoSectionVM, child) { + return Row( + mainAxisSize: MainAxisSize.min, + spacing: 12.h, + children: [ + Stack(children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() async { + if (appState.isAuthenticated) { + notificationsViewModel.setNotificationStatusID(2); + notificationsViewModel.getAllNotifications(); + Navigator.of(context).push( + CustomPageRoute( + page: NotificationsListPage(), + // page: LoginScreen(), + ), + ); + } else { + await authVM.onLoginPressed(); + } + }), + (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) + ? Positioned( + right: 0, + top: 0, + child: Container( + width: 8.w, + height: 8.h, + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + "", + style: TextStyle( + color: Colors.white, + fontSize: 8.f, + ), + textAlign: TextAlign.center, + ), + ), + ) + : SizedBox.shrink(), + ]), + Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() { + openIndoorNavigationBottomSheet(context); + }), + Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + ], + ); + }), ], ).paddingSymmetrical(24.h, 0.h), !appState.isAuthenticated @@ -329,208 +368,7 @@ class _LandingPageState extends State { builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return (myAppointmentsVM.isPatientHasQueueAppointment && index == 0) - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: myAppointmentsVM.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, - backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), - textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - ], - ), - SizedBox(height: 8.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 2.h), - "Thank you for your patience, here is your queue number." - .needTranslation - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 8.h), - myAppointmentsVM.currentPatientQueueDetails.queueNo!.toText28(isBold: true), - SizedBox(height: 6.h), - myAppointmentsVM.patientQueueDetailsList.isNotEmpty ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - "Serving Now: ".needTranslation.toText14(isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - myAppointmentsVM.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), - SizedBox(width: 8.w), - AppCustomChipWidget( - deleteIcon: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, - labelText: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, - iconColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - textColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - iconSize: 14.w, - backgroundColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor.withValues(alpha: 0.1) : AppColors.successColor.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), - ), - ], - ), - ], - ) : SizedBox(height: 12.h), - SizedBox(height: 5.h), - CustomButton( - text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), - onPressed: () {}, - backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), - borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), - textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus), - fontSize: 12.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ), - ], - ), - ), - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: AppointmentQueuePage(), - ), - ); - }) - : (immediateLiveCareVM.patientHasPendingLiveCareRequest && index == 0) - ? Column( - children: [ - SizedBox(height: 12.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Immediate LiveCare Request".needTranslation.toText16(isBold: true), - SizedBox(height: 10.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - AppCustomChipWidget( - labelText: immediateLiveCareVM.patientLiveCareHistoryList[0].stringCallStatus, - backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), - textColor: AppColors.alertColor, - ), - SizedBox(width: 8.w), - AppCustomChipWidget( - icon: AppAssets.appointment_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(immediateLiveCareVM.patientLiveCareHistoryList[0].arrivalTime), false)), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), - ], - ), - SizedBox(height: 10.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 8.h), - "Your turn is after ${immediateLiveCareVM.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), - SizedBox(height: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Expected waiting time: ".needTranslation.toText12(isBold: true), - SizedBox(height: 7.h), - ValueListenableBuilder( - valueListenable: immediateLiveCareVM.durationNotifier, - builder: (context, duration, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTime(duration), - ], - ); - }, - ), - ], - ), - // CustomButton( - // text: "View Details".needTranslation, - // onPressed: () async { - // Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - // }, - // backgroundColor: Color(0xffFEE9EA), - // borderColor: Color(0xffFEE9EA), - // textColor: Color(0xffED1C2B), - // fontSize: 14.f, - // fontWeight: FontWeight.w500, - // borderRadius: 12.r, - // padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - // height: 40.h, - // ), - ], - ), - ), - ).paddingSymmetrical(0.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - }), - SizedBox(height: 12.h), - ], - ) - : (todoSectionVM.patientAncillaryOrdersList.isNotEmpty && index == 1) - ? AncillaryOrderCard( - order: todoSectionVM.patientAncillaryOrdersList.first, - isLoading: false, - isOrdersList: false, - onCheckIn: () { - log("Check-in for order: ${todoSectionVM.patientAncillaryOrdersList.first.orderNo}"); - }, - onViewDetails: () { - Navigator.of(context).push( - CustomPageRoute( - page: AncillaryOrderDetailsList( - appointmentNoVida: todoSectionVM.patientAncillaryOrdersList.first.appointmentNo ?? 0, - orderNo: todoSectionVM.patientAncillaryOrdersList.first.orderNo ?? 0, - projectID: todoSectionVM.patientAncillaryOrdersList.first.projectID ?? 0, - projectName: todoSectionVM.patientAncillaryOrdersList.first.projectName ?? "", - ), - ), - ); - }, - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: - myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); + return getIndexSwiperCard(index); }, ) : Container( @@ -771,6 +609,288 @@ class _LandingPageState extends State { ); } + Widget getIndexSwiperCard(int index) { + if (index == 0) { + if (myAppointmentsViewModel.isPatientHasQueueAppointment) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), width: 2.w), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: myAppointmentsViewModel.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, + backgroundColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.20), + textColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + ], + ), + SizedBox(height: 8.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 2.h), + "Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + SizedBox(height: 8.h), + myAppointmentsViewModel.currentPatientQueueDetails.queueNo!.toText28(isBold: true), + SizedBox(height: 6.h), + myAppointmentsViewModel.patientQueueDetailsList.isNotEmpty + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + "Serving Now: ".needTranslation.toText14(isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + myAppointmentsViewModel.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, + iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 + ? AppColors.primaryRedColor.withValues(alpha: 0.1) + : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + ), + ], + ), + ], + ) + : SizedBox(height: 12.h), + SizedBox(height: 5.h), + CustomButton( + text: Utils.getCardButtonText(myAppointmentsViewModel.currentQueueStatus, myAppointmentsViewModel.currentPatientQueueDetails.roomNo ?? ""), + onPressed: () {}, + backgroundColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus), + borderColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.01), + textColor: Utils.getCardButtonTextColor(myAppointmentsViewModel.currentQueueStatus), + fontSize: 12.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + ], + ), + ), + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: AppointmentQueuePage(), + ), + ); + }); + } else if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + return Column( + children: [ + SizedBox(height: 12.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate LiveCare Request".needTranslation.toText16(isBold: true), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppCustomChipWidget( + labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), + ], + ), + SizedBox(height: 10.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), + SizedBox(height: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Expected waiting time: ".needTranslation.toText12(isBold: true), + SizedBox(height: 7.h), + ValueListenableBuilder( + valueListenable: immediateLiveCareViewModel.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(0.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + }), + SizedBox(height: 12.h), + ], + ); + } else if (myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } else if (index == 1) { + if (myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } else if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { + return AncillaryOrderCard( + order: todoSectionViewModel.patientAncillaryOrdersList.first, + isLoading: false, + isOrdersList: false, + onCheckIn: () { + log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); + }, + onViewDetails: () { + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderDetailsList( + appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, + orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, + projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, + projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", + ), + ), + ); + }, + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } else if (index == 2) { + if ((myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) && + myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { + return AncillaryOrderCard( + order: todoSectionViewModel.patientAncillaryOrdersList.first, + isLoading: false, + isOrdersList: false, + onCheckIn: () { + log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); + }, + onViewDetails: () { + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderDetailsList( + appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, + orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, + projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, + projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", + ), + ), + ); + }, + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } + return Container(); + } + void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( context, @@ -829,4 +949,41 @@ class _LandingPageState extends State { isFullScreen: false, ); } + + void openIndoorNavigationBottomSheet(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + context, + child: ChangeNotifierProvider( + create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.openPenguin(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().clearSearchText(); + }, + ); + } } diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 6216880..e74100c 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -8,7 +9,9 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; @@ -126,6 +129,28 @@ class SmallServiceCard extends StatelessWidget { case "indoor_navigation": openIndoorNavigationBottomSheet(context); + + case "search_doctor": + Navigator.of(context).push( + CustomPageRoute( + page: SearchDoctorByName(), + ), + ); + break; + case "health_calculators_and_converts": + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorsPage(type: HealthCalConEnum.calculator), + ), + ); + break; + case "health_converters": + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorsPage(type: HealthCalConEnum.converter), + ), + ); + break; default: // Handle unknown service break; diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart new file mode 100644 index 0000000..532b583 --- /dev/null +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +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/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class NotificationsListPage extends StatelessWidget { + const NotificationsListPage({super.key}); + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Notifications".needTranslation, + child: SingleChildScrollView( + child: Consumer(builder: (context, notificationsVM, child) { + return Container( + margin: EdgeInsets.symmetric(vertical: 24.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: ListView.builder( + itemCount: notificationsVM.isNotificationsLoading ? 4 : notificationsVM.notificationsList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return notificationsVM.isNotificationsLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : 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, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + "Notification Title".toText14(), + SizedBox(height: 8.h), + notificationsVM.notificationsList[index].message!.toText14(), + SizedBox(height: 12.h), + 1.divider, + ], + ), + ), + ), + ), + ); + }).paddingSymmetrical(16.w, 0.h), + ).paddingSymmetrical(24.w, 0.h); + }), + ), + ); + } +} diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index fdfcc04..054108d 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -266,7 +266,11 @@ class _AncillaryOrderPaymentPageState extends State { fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { if (!todoSectionViewModel.isProcessingPayment) { - _startApplePay(); + if (Utils.havePrivilege(103)) { + _startApplePay(); + } else { + _openPaymentURL("ApplePay"); + } } }) : SizedBox(height: 12.h), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 5409fcf..0580776 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -89,7 +89,7 @@ class CollapsingListView extends StatelessWidget { ? Transform.flip( flipX: appState.isArabic(), child: IconButton( - icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 24.h, height: 24.h), + icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.forward_top_nav_icon, width: 24.h, height: 24.h), padding: EdgeInsets.only(left: 12), onPressed: () { if (leadingCallback != null) { From b815affe5cbd3f4387620e780db89a1fb5742d57 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Sun, 11 Jan 2026 10:55:16 +0300 Subject: [PATCH 19/46] blood group --- assets/images/svg/blood_type.svg | 4 + assets/images/svg/genderInputIcon.svg | 5 + lib/core/api_consts.dart | 6 + lib/core/app_assets.dart | 2 + lib/core/dependencies.dart | 30 +- .../blood_donation/blood_donation_repo.dart | 196 ++++++++++++- .../blood_donation_view_model.dart | 177 ++++++++++-- .../models/blood_group_hospitals_model.dart | 81 ++++++ .../widgets/hospital_selection.dart | 86 ++++++ .../hospital_bottom_sheet_body.dart | 3 +- .../blood_donation/blood_donation_page.dart | 194 ++++++++++--- .../widgets/select_city_widget.dart | 4 +- .../widgets/select_gender_widget.dart | 82 +++--- .../book_appointment_page.dart | 261 ++++++++---------- .../laser/laser_appointment.dart | 1 + .../book_appointment/select_clinic_page.dart | 4 +- 16 files changed, 866 insertions(+), 270 deletions(-) create mode 100644 assets/images/svg/blood_type.svg create mode 100644 assets/images/svg/genderInputIcon.svg create mode 100644 lib/features/blood_donation/models/blood_group_hospitals_model.dart create mode 100644 lib/features/blood_donation/widgets/hospital_selection.dart diff --git a/assets/images/svg/blood_type.svg b/assets/images/svg/blood_type.svg new file mode 100644 index 0000000..5aded31 --- /dev/null +++ b/assets/images/svg/blood_type.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/genderInputIcon.svg b/assets/images/svg/genderInputIcon.svg new file mode 100644 index 0000000..4482ae3 --- /dev/null +++ b/assets/images/svg/genderInputIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 0febdbe..1213344 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -848,6 +848,12 @@ class ApiConsts { static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New"; static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; + //Blood Donation + static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType"; + static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation"; + static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics"; + static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots"; + // ************ static values for Api **************** static final double appVersionID = 50.3; static final int appChannelId = 3; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 4d5535f..accbb9c 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -218,6 +218,8 @@ class AppAssets { static const String activity = '$svgBasePath/activity.svg'; static const String age = '$svgBasePath/age_icon.svg'; static const String gender = '$svgBasePath/gender_icon.svg'; + static const String genderInputIcon = '$svgBasePath/genderInputIcon.svg'; + static const String bloodType = '$svgBasePath/blood_type.svg'; static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg'; static const String trade_down_red = '$svgBasePath/trade_down_red.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 489af59..4cb9c75 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -148,26 +148,20 @@ class AppDependencies { () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), ); - getIt.registerLazySingleton( - () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton( - () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); - getIt.registerLazySingleton( - () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel(payfortRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( - () => HabibWalletViewModel( - habibWalletRepo: getIt(), - errorHandlerService: getIt() - ), + () => HabibWalletViewModel(habibWalletRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( @@ -179,12 +173,7 @@ class AppDependencies { getIt.registerLazySingleton( () => BookAppointmentsViewModel( - bookAppointmentsRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt(), - myAppointmentsViewModel: getIt(), - locationUtils: getIt(), - dialogService: getIt()), + bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()), ); getIt.registerLazySingleton( @@ -198,13 +187,7 @@ class AppDependencies { getIt.registerLazySingleton( () => AuthenticationViewModel( - authenticationRepo: getIt(), - cacheService: getIt(), - navigationService: getIt(), - dialogService: getIt(), - appState: getIt(), - errorHandlerService: getIt(), - localAuthService: getIt()), + authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), ); getIt.registerLazySingleton(() => ProfileSettingsViewModel()); @@ -265,6 +248,7 @@ class AppDependencies { navigationService: getIt(), dialogService: getIt(), appState: getIt(), + navServices: getIt(), ), ); diff --git a/lib/features/blood_donation/blood_donation_repo.dart b/lib/features/blood_donation/blood_donation_repo.dart index dce0975..5643635 100644 --- a/lib/features/blood_donation/blood_donation_repo.dart +++ b/lib/features/blood_donation/blood_donation_repo.dart @@ -3,14 +3,26 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class BloodDonationRepo { Future>>> getAllCities(); + Future>>> getProjectList(); + + Future>>> getBloodDonationProjectsList(); + Future>> getPatientBloodGroupDetails(); + + Future>> updateBloodGroup({required Map request}); + + Future>> getFreeBloodDonationSlots({required Map request}); + + Future>> addUserAgreementForBloodDonation({required Map request}); } class BloodDonationRepoImp implements BloodDonationRepo { @@ -93,4 +105,186 @@ class BloodDonationRepoImp implements BloodDonationRepo { return Left(UnknownFailure(e.toString())); } } -} \ No newline at end of file + + @override + Future>>> getProjectList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_LIST, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListProject']; + + final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } 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>>> getBloodDonationProjectsList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getProjectsHaveBDClinics, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final listData = (response['BD_getProjectsHaveBDClinics'] as List); + final list = listData.map((item) => BdGetProjectsHaveBdClinic.fromJson(item as Map)).toList(); + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: list, + ); + } 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>>> updateBloodGroup({required Map request}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.bloodGroupUpdate, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['ListProject']; + + // final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getFreeBloodDonationSlots({required Map request}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getClinicsBDFreeSlots, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['ListProject']; + + // final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> addUserAgreementForBloodDonation({required Map request}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.userAgreementForBloodGroupUpdate, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['ListProject']; + + // final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/blood_donation/blood_donation_view_model.dart b/lib/features/blood_donation/blood_donation_view_model.dart index b8f0e9c..8325cf7 100644 --- a/lib/features/blood_donation/blood_donation_view_model.dart +++ b/lib/features/blood_donation/blood_donation_view_model.dart @@ -1,15 +1,25 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/doctor_response_mapper.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_list_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class BloodDonationViewModel extends ChangeNotifier { final DialogService dialogService; @@ -17,8 +27,21 @@ class BloodDonationViewModel extends ChangeNotifier { ErrorHandlerService errorHandlerService; final NavigationService navigationService; final AppState appState; + bool isTermsAccepted = false; + BdGetProjectsHaveBdClinic? selectedHospital; + CitiesModel? selectedCity; + BloodGroupListModel? selectedBloodGroup; + int _selectedHospitalIndex = 0; + int _selectedBloodTypeIndex = 0; + GenderTypeEnum? selectedGender; + String? selectedBloodType; + final NavigationService navServices; + + List hospitalList = []; List citiesList = []; + List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel(); + List bloodGroupList = [ BloodGroupListModel("O+", 0), BloodGroupListModel("O-", 1), @@ -30,35 +53,34 @@ class BloodDonationViewModel extends ChangeNotifier { BloodGroupListModel("B-", 7), ]; - List genderList = [ - BloodGroupListModel(LocaleKeys.malE.tr(), 1), - BloodGroupListModel("Female".needTranslation.tr(), 2), - ]; - - late CitiesModel selectedCity; - late BloodGroupListModel selectedBloodGroup; - int _selectedHospitalIndex = 0; - int _selectedBloodTypeIndex = 0; - String selectedBloodType = ''; - - List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel(); - - BloodDonationViewModel({required this.bloodDonationRepo, required this.errorHandlerService, required this.navigationService, required this.dialogService, required this.appState}); + BloodDonationViewModel({ + required this.bloodDonationRepo, + required this.errorHandlerService, + required this.navigationService, + required this.dialogService, + required this.appState, + required this.navServices, + }); setSelectedCity(CitiesModel city) { selectedCity = city; notifyListeners(); } + void onGenderChange(String? status) { + selectedGender = GenderTypeExtension.fromType(status)!; + notifyListeners(); + } + setSelectedBloodGroup(BloodGroupListModel bloodGroup) { selectedBloodGroup = bloodGroup; - selectedBloodType = selectedBloodGroup.name; + selectedBloodType = selectedBloodGroup!.name; notifyListeners(); } Future getRegionSelectedClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { citiesList.clear(); - selectedCity = CitiesModel(); + selectedCity = null; notifyListeners(); final result = await bloodDonationRepo.getAllCities(); @@ -71,6 +93,7 @@ class BloodDonationViewModel extends ChangeNotifier { onError!(apiResponse.errorMessage ?? 'An unexpected error occurred'); } else if (apiResponse.messageStatus == 1) { citiesList = apiResponse.data!; + citiesList.sort((a, b) => a.description!.compareTo(b.description!)); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -100,7 +123,7 @@ class BloodDonationViewModel extends ChangeNotifier { citiesModel.descriptionN = citiesList[_selectedHospitalIndex].descriptionN; selectedCity = citiesModel; selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!; - _selectedBloodTypeIndex = getBloodIndex(selectedBloodType); + _selectedBloodTypeIndex = getBloodIndex(selectedBloodType ?? ''); notifyListeners(); if (onSuccess != null) { @@ -113,11 +136,11 @@ class BloodDonationViewModel extends ChangeNotifier { int getSelectedCityID() { int cityID = 1; - citiesList.forEach((element) { + for (var element in citiesList) { if (element.description == patientBloodGroupDetailsModel.city) { cityID = element.iD!; } - }); + } return cityID; } @@ -144,4 +167,120 @@ class BloodDonationViewModel extends ChangeNotifier { return 0; } } + + void onTermAccepted() { + isTermsAccepted = !isTermsAccepted; + notifyListeners(); + } + + bool isUserAuthanticated() { + print("the app state is ${appState.isAuthenticated}"); + if (!appState.isAuthenticated) { + return false; + } else { + return true; + } + } + + Future fetchHospitalsList() async { + // hospitalList.clear(); + notifyListeners(); + final result = await bloodDonationRepo.getBloodDonationProjectsList(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + hospitalList = apiResponse.data!; + hospitalList.sort((a, b) => a.projectName!.compareTo(b.projectName!)); + notifyListeners(); + } + }, + ); + } + + Future getFreeBloodDonationSlots({required Map request}) async { + final result = await bloodDonationRepo.getFreeBloodDonationSlots(request: request); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + // TODO: Handle free slots data + print(apiResponse.data['BD_FreeSlots']); + notifyListeners(); + } + }, + ); + } + + bool isLocationEnabled() { + return appState.userLong != 0.0 && appState.userLong != 0.0; + } + + setSelectedHospital(BdGetProjectsHaveBdClinic hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + Future validateSelections() async { + if (selectedCity == null) { + await dialogService.showErrorBottomSheet( + message: "Please choose city", + ); + return false; + } + + if (selectedBloodGroup == null) { + await dialogService.showErrorBottomSheet( + message: "Please choose Gender", + ); + return false; + } + + if (selectedBloodType == null) { + await dialogService.showErrorBottomSheet( + message: "Please choose Blood Group", + ); + return false; + } + + if (!isTermsAccepted) { + await dialogService.showErrorBottomSheet( + message: "Please accept Terms and Conditions to continue", + ); + return false; + } + return true; + } + + Future updateBloodGroup() async { + LoaderBottomSheet.showLoader(); + // body['City'] = detailsModel.city; + // body['cityCode'] = detailsModel.cityCode; + // body['Gender'] = detailsModel.gender; + // body['BloodGroup'] = detailsModel.bloodGroup; + // body['CellNumber'] = user.mobileNumber; + // body['LanguageID'] = languageID; + // body['NationalID'] = user.nationalityID; + // body['ZipCode'] = user.zipCode ?? "+966"; + // body['isDentalAllowedBackend'] = false; + Map payload = { + "City": selectedCity?.description, + "cityCode": selectedCity?.iD, + "Gender": selectedGender?.value, + "isDentalAllowedBackend": false + // "Gender": selectedGender?.value, + }; + await bloodDonationRepo.updateBloodGroup(request: payload); + await addUserAgreementForBloodDonation(); + LoaderBottomSheet.hideLoader(); + } + + Future addUserAgreementForBloodDonation() async { + Map payload = {"IsAgreed": true}; + await bloodDonationRepo.addUserAgreementForBloodDonation(request: payload); + } } diff --git a/lib/features/blood_donation/models/blood_group_hospitals_model.dart b/lib/features/blood_donation/models/blood_group_hospitals_model.dart new file mode 100644 index 0000000..10b4e67 --- /dev/null +++ b/lib/features/blood_donation/models/blood_group_hospitals_model.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; + +class BdProjectsHaveBdClinicsModel { + List? bdGetProjectsHaveBdClinics; + + BdProjectsHaveBdClinicsModel({ + this.bdGetProjectsHaveBdClinics, + }); + + factory BdProjectsHaveBdClinicsModel.fromRawJson(String str) => BdProjectsHaveBdClinicsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory BdProjectsHaveBdClinicsModel.fromJson(Map json) => BdProjectsHaveBdClinicsModel( + bdGetProjectsHaveBdClinics: json["BD_getProjectsHaveBDClinics"] == null ? [] : List.from(json["BD_getProjectsHaveBDClinics"]!.map((x) => BdGetProjectsHaveBdClinic.fromJson(x))), + ); + + Map toJson() => { + "BD_getProjectsHaveBDClinics": bdGetProjectsHaveBdClinics == null ? [] : List.from(bdGetProjectsHaveBdClinics!.map((x) => x.toJson())), + }; +} + +class BdGetProjectsHaveBdClinic { + int? rowId; + int? id; + int? projectId; + int? numberOfRooms; + bool? isActive; + int? createdBy; + String? createdOn; + dynamic editedBy; + dynamic editedOn; + String? projectName; + dynamic projectNameN; + + BdGetProjectsHaveBdClinic({ + this.rowId, + this.id, + this.projectId, + this.numberOfRooms, + this.isActive, + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.projectName, + this.projectNameN, + }); + + factory BdGetProjectsHaveBdClinic.fromRawJson(String str) => BdGetProjectsHaveBdClinic.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory BdGetProjectsHaveBdClinic.fromJson(Map json) => BdGetProjectsHaveBdClinic( + rowId: json["RowID"], + id: json["ID"], + projectId: json["ProjectID"], + numberOfRooms: json["NumberOfRooms"], + isActive: json["IsActive"], + createdBy: json["CreatedBy"], + createdOn: json["CreatedOn"], + editedBy: json["EditedBy"], + editedOn: json["EditedON"], + projectName: json["ProjectName"], + projectNameN: json["ProjectNameN"], + ); + + Map toJson() => { + "RowID": rowId, + "ID": id, + "ProjectID": projectId, + "NumberOfRooms": numberOfRooms, + "IsActive": isActive, + "CreatedBy": createdBy, + "CreatedOn": createdOn, + "EditedBy": editedBy, + "EditedON": editedOn, + "ProjectName": projectName, + "ProjectNameN": projectNameN, + }; +} diff --git a/lib/features/blood_donation/widgets/hospital_selection.dart b/lib/features/blood_donation/widgets/hospital_selection.dart new file mode 100644 index 0000000..288ac34 --- /dev/null +++ b/lib/features/blood_donation/widgets/hospital_selection.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:provider/provider.dart'; + +class HospitalBottomSheetBodySelection extends StatelessWidget { + final Function(BdGetProjectsHaveBdClinic userSelection) onUserHospitalSelection; + + const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection)}); + + @override + Widget build(BuildContext context) { + final bloodDonationVm = Provider.of(context, listen: false); + AppState appState = getIt.get(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Please select the hospital you want to make an appointment.".needTranslation, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 16.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + hospitalName(bloodDonationVm.hospitalList[index]).onPress(() { + onUserHospitalSelection(bloodDonationVm.hospitalList[index]); + Navigator.of(context).pop(); + }) + ], + ), + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, width: 40.h, height: 40.h, fit: BoxFit.contain), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).onPress(() { + bloodDonationVm.setSelectedHospital(bloodDonationVm.hospitalList[index]); + Navigator.of(context).pop(); + }); + }, + separatorBuilder: (_, __) => SizedBox(height: 16.h), + itemCount: bloodDonationVm.hospitalList.length), + ) + ], + ); + } + + Widget hospitalName(dynamic hospital) => Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10), + Expanded( + child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: AppColors.blackColor)), + ) + ], + ); +} diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index ad48a6d..b167441 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -48,8 +48,7 @@ class HospitalBottomSheetBody extends StatelessWidget { hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - appointmentsViewModel.filterHospitalListByString( - value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name); + appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name); }, isEnable: true, prefix: null, diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index 5f3fa86..a987220 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -7,16 +7,26 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; +import 'package:hmg_patient_app_new/features/blood_donation/widgets/hospital_selection.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; class BloodDonationPage extends StatelessWidget { BloodDonationPage({super.key}); @@ -25,7 +35,7 @@ class BloodDonationPage extends StatelessWidget { @override Widget build(BuildContext context) { - appState = getIt.get(); + appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, bloodDonationVM, child) { @@ -34,15 +44,83 @@ class BloodDonationPage extends StatelessWidget { Expanded( child: CollapsingListView( title: LocaleKeys.bloodDonation.tr(), + trailing: CustomButton( + text: "Book", + onPressed: () { + // if (bloodDonationVM.isUserAuthanticated()) { + bloodDonationVM.fetchHospitalsList().then((value) { + showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer(builder: (_, data, __) { + return HospitalBottomSheetBodySelection( + onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) { + print("============User Choice==============="); + + bloodDonationVM.getFreeBloodDonationSlots(request: {"ClinicID": 134, "ProjectID": userChoice.projectId}); + }, + ); + }), callBackFunc: () {}); + }); + // } else { + // return showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.notice.tr(context: context), + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + // SizedBox(height: 8.h), + // (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.cancel.tr(), + // onPressed: () { + // Navigator.of(context).pop(); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // icon: AppAssets.cancel, + // iconColor: AppColors.primaryRedColor, + // ), + // ), + // SizedBox(width: 8.h), + // Expanded( + // child: CustomButton( + // text: LocaleKeys.confirm.tr(), + // onPressed: () async { + // Navigator.of(context).pop(); + // // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + // await getIt().onLoginPressed(); + // }, + // backgroundColor: AppColors.bgGreenColor, + // borderColor: AppColors.bgGreenColor, + // textColor: Colors.white, + // icon: AppAssets.confirm, + // ), + // ), + // ], + // ), + // SizedBox(height: 16.h), + // ], + // ).center, + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } + }, + backgroundColor: AppColors.bgRedLightColor, + borderColor: AppColors.bgRedLightColor, + textColor: AppColors.primaryRedColor, + padding: EdgeInsetsGeometry.symmetric(vertical: 0.h, horizontal: 20.h)), child: Padding( padding: EdgeInsets.all(24.w), child: SingleChildScrollView( child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), child: Padding( padding: EdgeInsets.all(16.h), child: Column( @@ -60,8 +138,8 @@ class BloodDonationPage extends StatelessWidget { children: [ LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), (appState.isArabic() - ? (bloodDonationVM.selectedCity.descriptionN ?? LocaleKeys.select.tr()) - : bloodDonationVM.selectedCity.description ?? LocaleKeys.select.tr(context: context)) + ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) + : bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), @@ -71,12 +149,7 @@ class BloodDonationPage extends StatelessWidget { ], ).onPress(() async { showCommonBottomSheetWithoutHeight(context, - title: LocaleKeys.selectCity.tr(context: context), - isDismissible: true, - child: SelectCityWidget( - bloodDonationViewModel: bloodDonationVM, - ), - callBackFunc: () {}); + title: LocaleKeys.selectCity.tr(context: context), isDismissible: true, child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), @@ -86,13 +159,16 @@ class BloodDonationPage extends StatelessWidget { children: [ Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h), + Utils.buildSvgWithAssets(icon: AppAssets.genderInputIcon, width: 40.h, height: 40.h), SizedBox(width: 12.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - "Male".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + (appState.isArabic() + ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) + : bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ], @@ -103,10 +179,7 @@ class BloodDonationPage extends StatelessWidget { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.selectGender.tr(context: context), isDismissible: true, - child: SelectGenderWidget( - isArabic: appState.isArabic(), - bloodDonationViewModel: bloodDonationVM, - ), + child: SelectGenderWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); }), SizedBox(height: 16.h), @@ -117,13 +190,17 @@ class BloodDonationPage extends StatelessWidget { children: [ Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h), + Utils.buildSvgWithAssets(icon: AppAssets.bloodType, width: 40.h, height: 40.h), SizedBox(width: 12.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - bloodDonationVM.selectedBloodType.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + (appState.isArabic() + ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) + : bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500) ], ), ], @@ -134,10 +211,7 @@ class BloodDonationPage extends StatelessWidget { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.select.tr(context: context), isDismissible: true, - child: SelectBloodGroupWidget( - isArabic: appState.isArabic(), - bloodDonationViewModel: bloodDonationVM, - ), + child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); }), ], @@ -149,19 +223,73 @@ class BloodDonationPage extends StatelessWidget { ), ), Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + GestureDetector( + onTap: bloodDonationVM.onTermAccepted, + child: Row( + children: [ + Selector( + selector: (_, viewModel) => viewModel.isTermsAccepted, + shouldRebuild: (previous, next) => previous != next, + builder: (context, isTermsAccepted, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), + ), + child: isTermsAccepted ? Icon(Icons.check, size: 16.f, color: Colors.white) : null, + ); + }, + ), + SizedBox(width: 12.h), + Row( + children: [ + Text( + LocaleKeys.iAcceptThe.tr(), + style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + ), + GestureDetector( + onTap: () { + // Navigate to terms and conditions page + Navigator.of(context).pushNamed('/terms'); + }, + child: Text( + LocaleKeys.termsConditoins.tr(), + style: context.dynamicTextStyle( + fontSize: 14.f, + fontWeight: FontWeight.w500, + color: AppColors.primaryRedColor, + decoration: TextDecoration.underline, + decorationColor: AppColors.primaryRedBorderColor, + ), + ), + ), + ], + ), + // Expanded( + // child: Text( + // LocaleKeys.iAcceptTermsConditions.tr().split("the").first, + // style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + // ), + // ), + ], + ), + ).paddingOnly(left: 16.h, right: 16.h, top: 24.h), CustomButton( text: LocaleKeys.save.tr(), - onPressed: () { - // openDoctorScheduleCalendar(); + onPressed: () async { + DialogService dialogService = getIt.get(); + if (await bloodDonationVM.validateSelections()) { + bloodDonationVM.updateBloodGroup(); + } }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/blood_donation/widgets/select_city_widget.dart b/lib/presentation/blood_donation/widgets/select_city_widget.dart index 8e2f9a0..bf5992d 100644 --- a/lib/presentation/blood_donation/widgets/select_city_widget.dart +++ b/lib/presentation/blood_donation/widgets/select_city_widget.dart @@ -26,9 +26,7 @@ class SelectCityWidget extends StatelessWidget { Navigator.of(context).pop(); }); }, - separatorBuilder: (_, __) => SizedBox( - height: 8.h, - ), + separatorBuilder: (_, __) => SizedBox(height: 8.h), itemCount: bloodDonationViewModel.citiesList.length), ) ], diff --git a/lib/presentation/blood_donation/widgets/select_gender_widget.dart b/lib/presentation/blood_donation/widgets/select_gender_widget.dart index 67cd4bb..0d360a8 100644 --- a/lib/presentation/blood_donation/widgets/select_gender_widget.dart +++ b/lib/presentation/blood_donation/widgets/select_gender_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.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'; @@ -8,10 +9,10 @@ import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_ import 'package:hmg_patient_app_new/theme/colors.dart'; class SelectGenderWidget extends StatelessWidget { - SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic}); + const SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic}); - BloodDonationViewModel bloodDonationViewModel; - bool isArabic; + final BloodDonationViewModel bloodDonationViewModel; + final bool isArabic; @override Widget build(BuildContext context) { @@ -20,46 +21,45 @@ class SelectGenderWidget extends StatelessWidget { children: [ SizedBox(height: 8.h), SizedBox( - height: MediaQuery.sizeOf(context).height * .4, - child: ListView.separated( - itemBuilder: (_, index) { - return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8.h, - children: [bloodDonationViewModel.genderList[index].name.toText16(color: AppColors.textColor, isBold: true)], + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [GenderTypeEnum.values[index].name.toCamelCase.toText16(color: AppColors.textColor, isBold: true)], + ), ), - ), - Transform.flip( - flipX: isArabic, - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 40.h, - height: 40.h, - fit: BoxFit.contain, + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), ), - ), - ], - ).paddingSymmetrical(16.h, 16.h).onPress(() { - // bloodDonationViewModel.setSelectedCity(bloodDonationViewModel.citiesList[index]); - Navigator.of(context).pop(); - })); - }, - separatorBuilder: (_, __) => SizedBox( - height: 8.h, - ), - itemCount: bloodDonationViewModel.genderList.length), - ) + ], + ).paddingSymmetrical(16.h, 16.h).onPress(() { + bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase); + Navigator.of(context).pop(); + })); + }, + separatorBuilder: (_, __) => SizedBox( + height: 8.h, + ), + itemCount: GenderTypeEnum.values.length)) ], ); } diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 5aa1b7c..0f63c43 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -111,10 +111,7 @@ class _BookAppointmentPageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith") - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: true), + ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), ], ) : myAppointmentsVM.patientMyDoctorsList.isEmpty @@ -240,10 +237,7 @@ class _BookAppointmentPageState extends State { ), ], ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); @@ -275,10 +269,7 @@ class _BookAppointmentPageState extends State { ), ], ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false); @@ -308,10 +299,7 @@ class _BookAppointmentPageState extends State { ), ], ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setProjectID(null); @@ -331,124 +319,115 @@ class _BookAppointmentPageState extends State { Column( children: [ Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() async { - //TODO Implement API to check for existing LiveCare Requests + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() async { + //TODO Implement API to check for existing LiveCare Requests - LoaderBottomSheet.showLoader(); - await immediateLiveCareViewModel.getPatientLiveCareHistory(); - LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.showLoader(); + await immediateLiveCareViewModel.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); - if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePendingRequestPage(), - ), - ); - } else { - Navigator.of(context).push( - CustomPageRoute( - page: SelectImmediateLiveCareClinicPage(), - ), - ); - } - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() { - bookAppointmentsViewModel.setIsClinicsListLoading(true); - bookAppointmentsViewModel.setIsLiveCareSchedule(true); - Navigator.of(context).push( - CustomPageRoute( - page: SelectClinicPage(), - ), - ); - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() { - openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); - }), - ], - ), - ), - ), - ], - ).paddingSymmetrical(24.h, 0.h) + if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + Navigator.of(context).push( + CustomPageRoute( + page: SelectImmediateLiveCareClinicPage(), + ), + ); + } + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() { + bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setIsLiveCareSchedule(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectClinicPage(), + ), + ); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() { + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); + }), + ], + ), + ), + ), + ], + ).paddingSymmetrical(24.h, 0.h) // : getLiveCareNotLoggedInUI() ; default: @@ -492,10 +471,8 @@ class _BookAppointmentPageState extends State { regionalViewModel.flush(); regionalViewModel.setBottomSheetType(type); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, - title: "", - titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), - isDismissible: false, child: Consumer(builder: (_, data, __) { + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, + child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); }), callBackFunc: () {}); } @@ -554,9 +531,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "No need to wait, you will get medical consultation immediately via video call" - .needTranslation - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -588,9 +563,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "A specialised doctor will contact you and will be able to view your medical history" - .needTranslation - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -606,9 +579,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "Offers free medicine delivery for the LiveCare appointment" - .needTranslation - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), diff --git a/lib/presentation/book_appointment/laser/laser_appointment.dart b/lib/presentation/book_appointment/laser/laser_appointment.dart index 19a3800..aae7990 100644 --- a/lib/presentation/book_appointment/laser/laser_appointment.dart +++ b/lib/presentation/book_appointment/laser/laser_appointment.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/string_extensions.dart' show CapExtension; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 450e86b..7fa3145 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -1184,9 +1184,7 @@ class _SelectClinicPageState extends State { bookAppointmentsViewModel.setIsContinueDentalPlan(true); Navigator.of(context).pop(); Navigator.of(context).push( - CustomPageRoute( - page: SelectDoctorPage(), - ), + CustomPageRoute(page: SelectDoctorPage()), ); }, backgroundColor: AppColors.bgGreenColor, From 38d83419637d7d10cc7a5832bed16633cb81e86d Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Sun, 11 Jan 2026 14:56:53 +0300 Subject: [PATCH 20/46] family file fix --- lib/features/medical_file/medical_file_repo.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 2f5cae6..09856ca 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -293,14 +293,13 @@ class MedicalFileRepoImp implements MedicalFileRepo { Failure? failure; await apiClient.post( ApiConsts.getAllSharedRecordsByStatus, - body: {if (status != null) "Status": status, "PatientID": patientID}, + body: {if (status != null) "Status": status, "PatientID": patientID.toString()}, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - final list = response['GetAllSharedRecordsByStatusList']; - + final list = response['GetAllSharedRecordsByStatusList'] ?? []; final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map)).toList().cast(); @@ -336,7 +335,7 @@ class MedicalFileRepoImp implements MedicalFileRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - final list = response['GetAllPendingRecordsList']; + final list = response['GetAllPendingRecordsList'] ?? []; final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( From 3f5bd0a759bcbda7da91e9745a72d22427cfb1bc Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 11 Jan 2026 17:01:07 +0300 Subject: [PATCH 21/46] Completed the health Tracker Module, Only the email part is left. --- assets/images/svg/blood_pressure_icon.svg | 4 + assets/images/svg/blood_sugar_only_icon.svg | 3 + assets/images/svg/low_indicator_icon.svg | 3 + .../images/svg/normal_status_green_icon.svg | 7 + assets/images/svg/send_email_icon.svg | 3 + assets/images/svg/weight_icon.svg | 3 + lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 40 +- lib/core/app_assets.dart | 19 +- lib/core/dependencies.dart | 17 +- lib/core/enums.dart | 18 +- lib/core/utils/size_utils.dart | 4 +- .../health_trackers/health_trackers_repo.dart | 752 +++++++++++ .../blood_pressure/blood_pressure_result.dart | 87 ++ .../month_blood_pressure_result_average.dart | 58 + .../week_blood_pressure_result_average.dart | 24 + .../year_blood_pressure_result_average.dart | 37 + .../blood_sugar/diabetic_patient_result.dart | 99 ++ .../month_diabetic_result_average.dart | 37 + .../week_diabetic_result_average.dart | 21 + .../year_diabetic_result_average.dart | 35 + ...nth_weight_measurement_result_average.dart | 37 + ...eek_weight_measurement_result_average.dart | 21 + .../weight/weight_measurement_result.dart | 77 ++ ...ear_weight_measurement_result_average.dart | 29 + .../water_monitor_view_model.dart | 29 +- lib/main.dart | 4 + .../health_calculator_detailed_page.dart | 49 +- .../health_calculator_view_model.dart | 25 +- .../add_health_tracker_entry_page.dart | 567 +++++++++ .../health_tracker_detail_page.dart | 1126 +++++++++++++++++ .../health_trackers/health_trackers_page.dart | 118 ++ .../health_trackers_view_model.dart | 1012 +++++++++++++++ .../widgets/tracker_last_value_card.dart | 271 ++++ .../hmg_services/services_page.dart | 28 +- lib/presentation/home/landing_page.dart | 2 +- .../organ_selector_screen.dart | 2 +- .../possible_conditions_screen.dart | 4 +- .../symptoms_checker/risk_factors_screen.dart | 2 +- .../symptoms_checker/suggestions_screen.dart | 2 +- .../symptoms_selector_screen.dart | 10 +- .../symptoms_checker/triage_screen.dart | 10 +- .../symptoms_checker/user_info_selection.dart | 61 +- ...creen.dart => water_consumption_page.dart} | 60 +- ....dart => water_monitor_settings_page.dart} | 9 +- .../widgets/water_action_buttons_widget.dart | 2 +- lib/routes/app_routes.dart | 62 +- lib/theme/colors.dart | 179 +-- lib/widgets/graph/custom_graph.dart | 33 +- lib/widgets/input_widget.dart | 56 +- lib/widgets/time_picker_widget.dart | 348 +++++ .../time_picker_widget_usage_example.dart | 165 +++ 52 files changed, 5396 insertions(+), 277 deletions(-) create mode 100644 assets/images/svg/blood_pressure_icon.svg create mode 100644 assets/images/svg/blood_sugar_only_icon.svg create mode 100644 assets/images/svg/low_indicator_icon.svg create mode 100644 assets/images/svg/normal_status_green_icon.svg create mode 100644 assets/images/svg/send_email_icon.svg create mode 100644 assets/images/svg/weight_icon.svg create mode 100644 lib/features/health_trackers/health_trackers_repo.dart create mode 100644 lib/features/health_trackers/models/blood_pressure/blood_pressure_result.dart create mode 100644 lib/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart create mode 100644 lib/features/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart create mode 100644 lib/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart create mode 100644 lib/features/health_trackers/models/blood_sugar/diabetic_patient_result.dart create mode 100644 lib/features/health_trackers/models/blood_sugar/month_diabetic_result_average.dart create mode 100644 lib/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart create mode 100644 lib/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart create mode 100644 lib/features/health_trackers/models/weight/month_weight_measurement_result_average.dart create mode 100644 lib/features/health_trackers/models/weight/week_weight_measurement_result_average.dart create mode 100644 lib/features/health_trackers/models/weight/weight_measurement_result.dart create mode 100644 lib/features/health_trackers/models/weight/year_weight_measurement_result_average.dart create mode 100644 lib/presentation/health_trackers/add_health_tracker_entry_page.dart create mode 100644 lib/presentation/health_trackers/health_tracker_detail_page.dart create mode 100644 lib/presentation/health_trackers/health_trackers_page.dart create mode 100644 lib/presentation/health_trackers/health_trackers_view_model.dart create mode 100644 lib/presentation/health_trackers/widgets/tracker_last_value_card.dart rename lib/presentation/water_monitor/{water_consumption_screen.dart => water_consumption_page.dart} (95%) rename lib/presentation/water_monitor/{water_monitor_settings_screen.dart => water_monitor_settings_page.dart} (97%) create mode 100644 lib/widgets/time_picker_widget.dart create mode 100644 lib/widgets/time_picker_widget_usage_example.dart diff --git a/assets/images/svg/blood_pressure_icon.svg b/assets/images/svg/blood_pressure_icon.svg new file mode 100644 index 0000000..0b027ad --- /dev/null +++ b/assets/images/svg/blood_pressure_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/blood_sugar_only_icon.svg b/assets/images/svg/blood_sugar_only_icon.svg new file mode 100644 index 0000000..f81cee8 --- /dev/null +++ b/assets/images/svg/blood_sugar_only_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/low_indicator_icon.svg b/assets/images/svg/low_indicator_icon.svg new file mode 100644 index 0000000..f2ca09f --- /dev/null +++ b/assets/images/svg/low_indicator_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/normal_status_green_icon.svg b/assets/images/svg/normal_status_green_icon.svg new file mode 100644 index 0000000..b3f2619 --- /dev/null +++ b/assets/images/svg/normal_status_green_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/svg/send_email_icon.svg b/assets/images/svg/send_email_icon.svg new file mode 100644 index 0000000..eb8684a --- /dev/null +++ b/assets/images/svg/send_email_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/weight_icon.svg b/assets/images/svg/weight_icon.svg new file mode 100644 index 0000000..f93c662 --- /dev/null +++ b/assets/images/svg/weight_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index f366329..039787b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; log("uri: ${Uri.parse(url.trim())}"); log("body: ${json.encode(body)}"); - // log("response.body: ${response.body}"); + log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 65ac1ef..246a801 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -398,19 +398,6 @@ var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_Get var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord'; var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions'; -///My Trackers -var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; -var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; -var ADD_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; - -var GET_BLOOD_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; -var GET_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; -var ADD_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; - -var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; -var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; -var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; - var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult'; @@ -420,7 +407,6 @@ var GET_QUESTION_TYPES = 'Services/OUTPs.svc/REST/getQuestionsTypes'; var UPDATE_DIABETIC_RESULT = 'Services/Patients.svc/REST/Patient_UpdateDiabeticResult'; -var SEND_AVERAGE_BLOOD_SUGAR_REPORT = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; var DEACTIVATE_DIABETIC_STATUS = 'services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; var DEACTIVATE_BLOOD_PRESSURES_STATUS = 'services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus'; @@ -826,7 +812,6 @@ class ApiConsts { static final String addHHCOrder = 'api/HHC/add'; // SYMPTOMS CHECKER API - static final String symptomsUserLogin = '$symptomsCheckerApi/user_login'; static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName'; static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors'; @@ -850,6 +835,31 @@ class ApiConsts { static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New"; static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; + // HEALTH TRACKERS + // Blood Sugar (Diabetic) + static String getDiabeticResultAverage = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; + static String getDiabeticResult = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; + static String addDiabeticResult = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; + static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult'; + static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; + static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; + + // Blood Pressure + static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; + static String getBloodPressureResult = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; + static String addBloodPressureResult = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; + static String updateBloodPressureResult = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult'; + static String deactivateBloodPressureStatus = 'Services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus'; + static String sendAverageBloodPressureReport = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport'; + + // Weight Measurement + static String getWeightMeasurementResultAverage = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; + static String getWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; + static String addWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; + static String updateWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult'; + static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus'; + static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport'; + // ************ static values for Api **************** static final double appVersionID = 50.3; static final int appChannelId = 3; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 2fdc389..741cb6b 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -267,16 +267,21 @@ class AppAssets { static const String yellowArrowDownIcon = '$svgBasePath/yellow_arrow_down_icon.svg'; static const String greenTickIcon = '$svgBasePath/green_tick_icon.svg'; - // PNGS - - static const String bloodSugar = '$svgBasePath/bloodsugar.svg'; static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg'; static const String triglycerides = '$svgBasePath/triglycerides.svg'; static const String bulb = '$svgBasePath/bulb.svg'; static const String switchBtn = '$svgBasePath/switch.svg'; + //Health Trackers + static const String bloodPressureIcon = '$svgBasePath/blood_pressure_icon.svg'; + static const String bloodSugarOnlyIcon = '$svgBasePath/blood_sugar_only_icon.svg'; + static const String weightIcon = '$svgBasePath/weight_icon.svg'; + static const String normalStatusGreenIcon = '$svgBasePath/normal_status_green_icon.svg'; + static const String sendEmailIcon = '$svgBasePath/send_email_icon.svg'; + static const String lowIndicatorIcon = '$svgBasePath/low_indicator_icon.svg'; + // Health Calculators static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg'; static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg'; static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg'; @@ -285,9 +290,7 @@ class AppAssets { static const String covid19icon = '$svgBasePath/covid_19.svg'; - //vital sign - static const String heartRate = '$svgBasePath/heart_rate.svg'; static const String respRate = '$svgBasePath/resp_rate.svg'; static const String weightVital = '$svgBasePath/weight_2.svg'; @@ -296,8 +299,6 @@ class AppAssets { static const String bloodPressure = '$svgBasePath/blood_pressure.svg'; static const String temperature = '$svgBasePath/temperature.svg'; - - // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; static const String liveCareService = '$pngBasePath/livecare_service.png'; @@ -324,8 +325,6 @@ class AppAssets { static const String fullBodyFront = '$pngBasePath/full_body_front.png'; static const String fullBodyBack = '$pngBasePath/full_body_back.png'; static const String bmiFullBody = '$pngBasePath/bmi_image_1.png'; - - } class AppAnimations { @@ -346,6 +345,4 @@ class AppAnimations { static const String ambulance = '$lottieBasePath/ambulance.json'; static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json'; static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json'; - - } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index c6c5554..f284b39 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; @@ -50,6 +51,7 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -141,6 +143,7 @@ class AppDependencies { getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => HealthTrackersRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -167,10 +170,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => HabibWalletViewModel( - habibWalletRepo: getIt(), - errorHandlerService: getIt() - ), + () => HabibWalletViewModel(habibWalletRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( @@ -273,8 +273,13 @@ class AppDependencies { getIt.registerLazySingleton(() => HealthProvider()); - getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt())); + getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton(() => MyInvoicesViewModel( + myInvoicesRepo: getIt(), + errorHandlerService: getIt(), + navServices: getIt(), + )); + getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); } } diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 8fd4818..6dc3bf6 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -16,7 +16,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates } enum CalenderEnum { gregorian, hijri } -enum SelectionTypeEnum { dropdown, calendar, search } +enum SelectionTypeEnum { dropdown, calendar, search, time } enum GenderTypeEnum { male, female } @@ -38,7 +38,19 @@ enum HealthCalConEnum { calculator, converter } enum HealthCalculatorEnum { general, women } -enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides } +enum HealthCalculatorsTypeEnum { + bmi, + calories, + bmr, + idealBodyWeight, + bodyFat, + crabsProteinFat, + ovulation, + deliveryDueDate, + bloodSugar, + bloodCholesterol, + triglycerides +} extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { String get displayName { @@ -297,3 +309,5 @@ extension ServiceTypeEnumExt on ServiceTypeEnum { // SymptomsChecker enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency } + +enum HealthTrackerTypeEnum { bloodSugar, bloodPressure, weightTracker } diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 4fdc09c..02b8195 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -7,16 +7,14 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your const num figmaDesignWidth = 375; // iPhone X / 12 base width const num figmaDesignHeight = 812; // iPhone X / 12 base height - extension ConstrainedResponsive on num { /// Width with max cap for tablets - double get wCapped => isTablet ? math.min( w, this * 1.3) : w; + double get wCapped => isTablet ? math.min(w, this * 1.3) : w; /// Height with max cap for tablets double get hCapped => isTablet ? math.min(h, this * 1.3) : h; } - extension ResponsiveExtension on num { double get _screenWidth => SizeUtils.width; diff --git a/lib/features/health_trackers/health_trackers_repo.dart b/lib/features/health_trackers/health_trackers_repo.dart new file mode 100644 index 0000000..e6930f9 --- /dev/null +++ b/lib/features/health_trackers/health_trackers_repo.dart @@ -0,0 +1,752 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +/// Progress types to request different ranges from the progress API. +enum ProgressType { today, week, month } + +abstract class HealthTrackersRepo { + // ==================== BLOOD SUGAR (DIABETIC) ==================== + /// Get blood sugar result averages (week, month, year). + Future>> getDiabeticResultAverage(); + + /// Get blood sugar results (week, month, year). + Future>> getDiabeticResults(); + + /// Add new blood sugar result. + Future>> addDiabeticResult({ + required String bloodSugarDateChart, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + }); + + /// Update existing blood sugar result. + Future>> updateDiabeticResult({ + required DateTime month, + required DateTime hour, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + required int lineItemNo, + }); + + /// Deactivate blood sugar record. + Future>> deactivateDiabeticStatus({ + required int lineItemNo, + }); + + // ==================== BLOOD PRESSURE ==================== + /// Get blood pressure result averages (week, month, year). + Future>> getBloodPressureResultAverage(); + + /// Get blood pressure results (week, month, year). + Future>> getBloodPressureResults(); + + /// Add new blood pressure result. + Future>> addBloodPressureResult({ + required String bloodPressureDate, + required String diastolicPressure, + required String systolicePressure, + required int measuredArm, + }); + + /// Update existing blood pressure result. + Future>> updateBloodPressureResult({ + required String bloodPressureDate, + required String diastolicPressure, + required String systolicePressure, + required int measuredArm, + required int lineItemNo, + }); + + /// Deactivate blood pressure record. + Future>> deactivateBloodPressureStatus({ + required int lineItemNo, + }); + + // ==================== WEIGHT MEASUREMENT ==================== + /// Get weight measurement result averages (week, month, year). + Future>> getWeightMeasurementResultAverage(); + + /// Get weight measurement results (week, month, year). + Future>> getWeightMeasurementResults(); + + /// Add new weight measurement result. + Future>> addWeightMeasurementResult({ + required String weightDate, + required String weightMeasured, + required int weightUnit, + }); + + /// Update existing weight measurement result. + Future>> updateWeightMeasurementResult({ + required int lineItemNo, + required int weightUnit, + required String weightMeasured, + required String weightDate, + }); + + /// Deactivate weight measurement record. + Future>> deactivateWeightMeasurementStatus({ + required int lineItemNo, + }); +} + +class HealthTrackersRepoImp implements HealthTrackersRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + HealthTrackersRepoImp({required this.loggerService, required this.apiClient}); + + // ==================== BLOOD SUGAR (DIABETIC) METHODS ==================== + + @override + Future>> getDiabeticResultAverage() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getDiabeticResultAverage, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract average lists + extracted = { + 'monthAverageList': response['List_MonthDiabtectResultAverage'] ?? [], + 'weekAverageList': response['List_WeekDiabtectResultAverage'] ?? [], + 'yearAverageList': response['List_YearDiabtecResultAverage'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> getDiabeticResults() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getDiabeticResult, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract patient result lists + extracted = { + 'monthResultList': response['List_MonthDiabtecPatientResult'] ?? [], + 'weekResultList': response['List_WeekDiabtecPatientResult'] ?? [], + 'yearResultList': response['List_YearDiabtecPatientResult'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> addDiabeticResult({ + required String bloodSugarDateChart, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'BloodSugerDateChart': bloodSugarDateChart, + 'BloodSugerResult': bloodSugarResult, + 'DiabtecUnit': diabeticUnit, + 'MeasuredTime': measuredTime + 1, // Add 1 as per old service + }; + + await apiClient.post( + ApiConsts.addDiabeticResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> updateDiabeticResult({ + required DateTime month, + required DateTime hour, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + required int lineItemNo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + // Format: 'YYYY-MM-DD HH:MM:SS' as per old service + String formattedDate = '${month.year}-${month.month}-${month.day} ${hour.hour}:${hour.minute}:00'; + + Map body = { + 'BloodSugerDateChart': formattedDate, + 'BloodSugerResult': bloodSugarResult, + 'DiabtecUnit': diabeticUnit, + 'MeasuredTime': measuredTime + 1, // Add 1 as per old service + 'LineItemNo': lineItemNo, + }; + + await apiClient.post( + ApiConsts.updateDiabeticResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> deactivateDiabeticStatus({ + required int lineItemNo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'LineItemNo': lineItemNo, + }; + + await apiClient.post( + ApiConsts.deactivateDiabeticStatus, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + // ==================== BLOOD PRESSURE METHODS ==================== + + @override + Future>> getBloodPressureResultAverage() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getBloodPressureResultAverage, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract all three list types + extracted = { + 'monthList': response['List_MonthBloodPressureResultAverage'] ?? [], + 'weekList': response['List_WeekBloodPressureResultAverage'] ?? [], + 'yearList': response['List_YearBloodPressureResultAverage'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> getBloodPressureResults() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getBloodPressureResult, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract all three list types + extracted = { + 'weekList': response['List_WeekBloodPressureResult'] ?? [], + 'monthList': response['List_MonthBloodPressureResult'] ?? [], + 'yearList': response['List_YearBloodPressureResult'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> addBloodPressureResult({ + required String bloodPressureDate, + required String diastolicPressure, + required String systolicePressure, + required int measuredArm, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'BloodPressureDate': bloodPressureDate, + 'DiastolicPressure': diastolicPressure, + 'SystolicePressure': systolicePressure, + 'MeasuredArm': measuredArm, + }; + + await apiClient.post( + ApiConsts.addBloodPressureResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> updateBloodPressureResult({ + required String bloodPressureDate, + required String diastolicPressure, + required String systolicePressure, + required int measuredArm, + required int lineItemNo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'BloodPressureDate': bloodPressureDate, + 'DiastolicPressure': diastolicPressure, + 'SystolicePressure': systolicePressure, + 'MeasuredArm': measuredArm, + 'LineItemNo': lineItemNo, + }; + + await apiClient.post( + ApiConsts.updateBloodPressureResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> deactivateBloodPressureStatus({ + required int lineItemNo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'LineItemNo': lineItemNo, + }; + + await apiClient.post( + ApiConsts.deactivateBloodPressureStatus, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + // ==================== WEIGHT MEASUREMENT METHODS ==================== + + @override + Future>> getWeightMeasurementResultAverage() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getWeightMeasurementResultAverage, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract average lists + extracted = { + 'monthAverageList': response['List_MonthWeightMeasurementResultAverage'] ?? [], + 'weekAverageList': response['List_WeekWeightMeasurementResultAverage'] ?? [], + 'yearAverageList': response['List_YearWeightMeasurementResultAverage'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> getWeightMeasurementResults() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getWeightMeasurementResult, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic extracted; + if (response is Map) { + // Extract result lists + extracted = { + 'weekResultList': response['List_WeekWeightMeasurementResult'] ?? [], + 'monthResultList': response['List_MonthWeightMeasurementResult'] ?? [], + 'yearResultList': response['List_YearWeightMeasurementResult'] ?? [], + }; + } else { + extracted = response; + } + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: extracted, + ); + } 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>> addWeightMeasurementResult({ + required String weightDate, + required String weightMeasured, + required int weightUnit, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'WeightDate': weightDate, + 'WeightMeasured': weightMeasured, + 'weightUnit': weightUnit, + }; + + await apiClient.post( + ApiConsts.addWeightMeasurementResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> updateWeightMeasurementResult({ + required int lineItemNo, + required int weightUnit, + required String weightMeasured, + required String weightDate, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'LineItemNo': lineItemNo, + 'weightUnit': '$weightUnit', // Convert to string as per old service + 'WeightMeasured': weightMeasured, + 'WeightDate': weightDate, + }; + + await apiClient.post( + ApiConsts.updateWeightMeasurementResult, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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>> deactivateWeightMeasurementStatus({ + required int lineItemNo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'LineItemNo': lineItemNo, + }; + + await apiClient.post( + ApiConsts.deactivateWeightMeasurementStatus, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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/health_trackers/models/blood_pressure/blood_pressure_result.dart b/lib/features/health_trackers/models/blood_pressure/blood_pressure_result.dart new file mode 100644 index 0000000..f024d8e --- /dev/null +++ b/lib/features/health_trackers/models/blood_pressure/blood_pressure_result.dart @@ -0,0 +1,87 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class BloodPressureResult { + int? patientID; + int? lineItemNo; + DateTime? bloodPressureDate; + int? measuredArm; + int? systolicePressure; + int? diastolicPressure; + dynamic remark; + bool? isActive; + int? chartYear; + String? chartMonth; + dynamic yearSystolicePressureAverageResult; + dynamic monthSystolicePressureResult; + dynamic weekSystolicePressureResult; + int? yearDiastolicPressureAverageResult; + dynamic monthDiastolicPressureResult; + dynamic weekDiastolicPressureResult; + String? measuredArmDesc; + dynamic weekDesc; + + BloodPressureResult( + {this.patientID, + this.lineItemNo, + this.bloodPressureDate, + this.measuredArm, + this.systolicePressure, + this.diastolicPressure, + this.remark, + this.isActive, + this.chartYear, + this.chartMonth, + this.yearSystolicePressureAverageResult, + this.monthSystolicePressureResult, + this.weekSystolicePressureResult, + this.yearDiastolicPressureAverageResult, + this.monthDiastolicPressureResult, + this.weekDiastolicPressureResult, + this.measuredArmDesc, + this.weekDesc}); + + BloodPressureResult.fromJson(Map json) { + patientID = json['PatientID']; + lineItemNo = json['LineItemNo']; + bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']); + measuredArm = json['MeasuredArm']; + systolicePressure = json['SystolicePressure']; + diastolicPressure = json['DiastolicPressure']; + remark = json['Remark']; + isActive = json['IsActive']; + chartYear = json['ChartYear']; + chartMonth = json['ChartMonth']; + yearSystolicePressureAverageResult = json['YearSystolicePressureAverageResult']; + monthSystolicePressureResult = json['MonthSystolicePressureResult']; + weekSystolicePressureResult = json['WeekSystolicePressureResult']; + yearDiastolicPressureAverageResult = json['YearDiastolicPressureAverageResult']; + monthDiastolicPressureResult = json['MonthDiastolicPressureResult']; + weekDiastolicPressureResult = json['WeekDiastolicPressureResult']; + measuredArmDesc = json['MeasuredArmDesc']; + weekDesc = json['WeekDesc']; + } + + Map toJson() { + final Map data = {}; + data['PatientID'] = patientID; + data['LineItemNo'] = lineItemNo; + data['BloodPressureDate'] = bloodPressureDate; + data['MeasuredArm'] = measuredArm; + data['SystolicePressure'] = systolicePressure; + data['DiastolicPressure'] = diastolicPressure; + data['Remark'] = remark; + data['IsActive'] = isActive; + data['ChartYear'] = chartYear; + data['ChartMonth'] = chartMonth; + data['YearSystolicePressureAverageResult'] = yearSystolicePressureAverageResult; + data['MonthSystolicePressureResult'] = monthSystolicePressureResult; + data['WeekSystolicePressureResult'] = weekSystolicePressureResult; + data['YearDiastolicPressureAverageResult'] = yearDiastolicPressureAverageResult; + data['MonthDiastolicPressureResult'] = monthDiastolicPressureResult; + data['WeekDiastolicPressureResult'] = weekDiastolicPressureResult; + data['MeasuredArmDesc'] = measuredArmDesc; + data['WeekDesc'] = weekDesc; + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart b/lib/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart new file mode 100644 index 0000000..3975805 --- /dev/null +++ b/lib/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart @@ -0,0 +1,58 @@ +class MonthBloodPressureResultAverage { + dynamic weekfourSystolicePressureAverageResult; + dynamic weekfourDiastolicPressureAverageResult; + dynamic weekthreeSystolicePressureAverageResult; + dynamic weekthreeDiastolicPressureAverageResult; + dynamic weektwoSystolicePressureAverageResult; + dynamic weektwoDiastolicPressureAverageResult; + dynamic weekoneSystolicePressureAverageResult; + dynamic weekoneDiastolicPressureAverageResult; + String? weekDesc; + int? weekDiastolicPressureAverageResult; + int? weekSystolicePressureAverageResult; + + MonthBloodPressureResultAverage({ + this.weekfourSystolicePressureAverageResult, + this.weekfourDiastolicPressureAverageResult, + this.weekthreeSystolicePressureAverageResult, + this.weekthreeDiastolicPressureAverageResult, + this.weektwoSystolicePressureAverageResult, + this.weektwoDiastolicPressureAverageResult, + this.weekoneSystolicePressureAverageResult, + this.weekoneDiastolicPressureAverageResult, + this.weekDesc, + this.weekDiastolicPressureAverageResult, + this.weekSystolicePressureAverageResult, + }); + + MonthBloodPressureResultAverage.fromJson(Map json) { + weekfourSystolicePressureAverageResult = json['weekfourSystolicePressureAverageResult']; + weekfourDiastolicPressureAverageResult = json['weekfourDiastolicPressureAverageResult']; + weekthreeSystolicePressureAverageResult = json['weekthreeSystolicePressureAverageResult']; + weekthreeDiastolicPressureAverageResult = json['weekthreeDiastolicPressureAverageResult']; + weektwoSystolicePressureAverageResult = json['weektwoSystolicePressureAverageResult']; + weektwoDiastolicPressureAverageResult = json['weektwoDiastolicPressureAverageResult']; + weekoneSystolicePressureAverageResult = json['weekoneSystolicePressureAverageResult']; + weekoneDiastolicPressureAverageResult = json['weekoneDiastolicPressureAverageResult']; + weekDesc = json['WeekDesc']; + weekDiastolicPressureAverageResult = json['WeekDiastolicPressureAverageResult']; + weekSystolicePressureAverageResult = json['WeekSystolicePressureAverageResult']; + } + + Map toJson() { + final Map data = {}; + data['weekfourSystolicePressureAverageResult'] = weekfourSystolicePressureAverageResult; + data['weekfourDiastolicPressureAverageResult'] = weekfourDiastolicPressureAverageResult; + data['weekthreeSystolicePressureAverageResult'] = weekthreeSystolicePressureAverageResult; + data['weekthreeDiastolicPressureAverageResult'] = weekthreeDiastolicPressureAverageResult; + data['weektwoSystolicePressureAverageResult'] = weektwoSystolicePressureAverageResult; + data['weektwoDiastolicPressureAverageResult'] = weektwoDiastolicPressureAverageResult; + data['weekoneSystolicePressureAverageResult'] = weekoneSystolicePressureAverageResult; + data['weekoneDiastolicPressureAverageResult'] = weekoneDiastolicPressureAverageResult; + data['WeekDesc'] = weekDesc; + data['WeekDiastolicPressureAverageResult'] = weekDiastolicPressureAverageResult; + data['WeekSystolicePressureAverageResult'] = weekSystolicePressureAverageResult; + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart b/lib/features/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart new file mode 100644 index 0000000..381d514 --- /dev/null +++ b/lib/features/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart @@ -0,0 +1,24 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class WeekBloodPressureResultAverage { + int? dailySystolicePressureAverageResult; + int? dailyDiastolicPressureAverageResult; + DateTime? bloodPressureDate; + + WeekBloodPressureResultAverage({this.dailySystolicePressureAverageResult, this.dailyDiastolicPressureAverageResult, this.bloodPressureDate}); + + WeekBloodPressureResultAverage.fromJson(Map json) { + dailySystolicePressureAverageResult = json['DailySystolicePressureAverageResult']; + dailyDiastolicPressureAverageResult = json['DailyDiastolicPressureAverageResult']; + bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']); + } + + Map toJson() { + final Map data = {}; + data['DailySystolicePressureAverageResult'] = dailySystolicePressureAverageResult; + data['DailyDiastolicPressureAverageResult'] = dailyDiastolicPressureAverageResult; + data['BloodPressureDate'] = bloodPressureDate; + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart b/lib/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart new file mode 100644 index 0000000..6f34246 --- /dev/null +++ b/lib/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart @@ -0,0 +1,37 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class YearBloodPressureResultAverage { + int? monthSystolicePressureAverageResult; + int? monthDiastolicPressureAverageResult; + dynamic monthNumber; + String? monthName; + String? yearName; + DateTime? date; + + YearBloodPressureResultAverage({ + this.monthSystolicePressureAverageResult, + this.monthDiastolicPressureAverageResult, + this.monthNumber, + this.monthName, + this.yearName, + }); + + YearBloodPressureResultAverage.fromJson(Map json) { + monthSystolicePressureAverageResult = json['monthSystolicePressureAverageResult']; + monthDiastolicPressureAverageResult = json['monthDiastolicPressureAverageResult']; + monthNumber = json['monthNumber']; + monthName = json['monthName']; + yearName = json['yearName']; + date = DateUtil.getMonthDateTime(monthName!, yearName); + } + + Map toJson() { + final Map data = {}; + data['monthSystolicePressureAverageResult'] = monthSystolicePressureAverageResult; + data['monthDiastolicPressureAverageResult'] = monthDiastolicPressureAverageResult; + data['monthNumber'] = monthNumber; + data['monthName'] = monthName; + data['yearName'] = yearName; + return data; + } +} diff --git a/lib/features/health_trackers/models/blood_sugar/diabetic_patient_result.dart b/lib/features/health_trackers/models/blood_sugar/diabetic_patient_result.dart new file mode 100644 index 0000000..066df3c --- /dev/null +++ b/lib/features/health_trackers/models/blood_sugar/diabetic_patient_result.dart @@ -0,0 +1,99 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class DiabeticPatientResult { + String? chartMonth; + var chartYear; + DateTime? dateChart; + var description; + var descriptionN; + int? diabtecAvarage; + bool? isActive; + int? lineItemNo; + var listMonth; + var listWeek; + int? measured; + String? measuredDesc; + var monthAverageResult; + int? patientID; + var remark; + var resultDesc; + dynamic resultValue; + String? unit; + var weekAverageResult; + String? weekDesc; + var yearAverageResult; + + DiabeticPatientResult( + {this.chartMonth, + this.chartYear, + this.dateChart, + this.description, + this.descriptionN, + this.diabtecAvarage, + this.isActive, + this.lineItemNo, + this.listMonth, + this.listWeek, + this.measured, + this.measuredDesc, + this.monthAverageResult, + this.patientID, + this.remark, + this.resultDesc, + this.resultValue, + this.unit, + this.weekAverageResult, + this.weekDesc, + this.yearAverageResult}); + + DiabeticPatientResult.fromJson(Map json) { + chartMonth = json['ChartMonth']; + chartYear = json['ChartYear']; + dateChart = DateUtil.convertStringToDate(json['DateChart']); + description = json['Description']; + descriptionN = json['DescriptionN']; + diabtecAvarage = json['DiabtecAvarage']; + isActive = json['IsActive']; + lineItemNo = json['LineItemNo']; + listMonth = json['List_Month']; + listWeek = json['List_Week']; + measured = json['Measured']; + measuredDesc = json['MeasuredDesc']; + monthAverageResult = json['MonthAverageResult']; + patientID = json['PatientID']; + remark = json['Remark']; + resultDesc = json['ResultDesc']; + resultValue = json['ResultValue']; + unit = json['Unit']; + weekAverageResult = json['WeekAverageResult']; + weekDesc = json['WeekDesc']; + yearAverageResult = json['YearAverageResult']; + } + + Map toJson() { + final Map data = {}; + data['ChartMonth'] = chartMonth; + data['ChartYear'] = chartYear; + data['DateChart'] = DateUtil.convertDateToString(dateChart!); + data['Description'] = description; + data['DescriptionN'] = descriptionN; + data['DiabtecAvarage'] = diabtecAvarage; + data['IsActive'] = isActive; + data['LineItemNo'] = lineItemNo; + data['List_Month'] = listMonth; + data['List_Week'] = listWeek; + data['Measured'] = measured; + data['MeasuredDesc'] = measuredDesc; + data['MonthAverageResult'] = monthAverageResult; + data['PatientID'] = patientID; + data['Remark'] = remark; + data['ResultDesc'] = resultDesc; + data['ResultValue'] = resultValue; + data['Unit'] = unit; + data['WeekAverageResult'] = weekAverageResult; + data['WeekDesc'] = weekDesc; + data['YearAverageResult'] = yearAverageResult; + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_sugar/month_diabetic_result_average.dart b/lib/features/health_trackers/models/blood_sugar/month_diabetic_result_average.dart new file mode 100644 index 0000000..77b06f1 --- /dev/null +++ b/lib/features/health_trackers/models/blood_sugar/month_diabetic_result_average.dart @@ -0,0 +1,37 @@ +class MonthDiabeticResultAverage { + var weekfourAverageResult; + var weekthreeAverageResult; + var weektwoAverageResult; + var weekoneAverageResult; + dynamic weekAverageResult; + String? weekDesc; + + MonthDiabeticResultAverage( + {this.weekfourAverageResult, + this.weekthreeAverageResult, + this.weektwoAverageResult, + this.weekoneAverageResult, + this.weekAverageResult, + this.weekDesc}); + + MonthDiabeticResultAverage.fromJson(Map json) { + weekfourAverageResult = json['weekfourAverageResult']; + weekthreeAverageResult = json['weekthreeAverageResult']; + weektwoAverageResult = json['weektwoAverageResult']; + weekoneAverageResult = json['weekoneAverageResult']; + weekAverageResult = json['WeekAverageResult']; + weekDesc = json['WeekDesc']; + } + + Map toJson() { + final Map data = {}; + data['weekfourAverageResult'] = weekfourAverageResult; + data['weekthreeAverageResult'] = weekthreeAverageResult; + data['weektwoAverageResult'] = weektwoAverageResult; + data['weekoneAverageResult'] = weekoneAverageResult; + data['WeekAverageResult'] = weekAverageResult; + data['WeekDesc'] = weekDesc; + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart b/lib/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart new file mode 100644 index 0000000..3b35fe7 --- /dev/null +++ b/lib/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart @@ -0,0 +1,21 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class WeekDiabeticResultAverage { + dynamic dailyAverageResult; + DateTime? dateChart; + + WeekDiabeticResultAverage({this.dailyAverageResult, this.dateChart}); + + WeekDiabeticResultAverage.fromJson(Map json) { + dailyAverageResult = json['DailyAverageResult']; + dateChart = DateUtil.convertStringToDate(json['DateChart']); + } + + Map toJson() { + final Map data = {}; + data['DailyAverageResult'] = dailyAverageResult; + data['DateChart'] = DateUtil.convertDateToString(dateChart!); + return data; + } +} + diff --git a/lib/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart b/lib/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart new file mode 100644 index 0000000..fb34056 --- /dev/null +++ b/lib/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart @@ -0,0 +1,35 @@ +import 'dart:developer'; + +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class YearDiabeticResultAverage { + dynamic monthAverageResult; + var monthNumber; + String? monthName; + String? yearName; + DateTime? date; + + YearDiabeticResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName}); + + YearDiabeticResultAverage.fromJson(Map json) { + try { + monthAverageResult = json['monthAverageResult']; + monthNumber = json['monthNumber']; + monthName = json['monthName']; + yearName = json['yearName']; + date = DateUtil.getMonthDateTime(monthName!, yearName); + } catch (e) { + log(e.toString()); + } + } + + Map toJson() { + final Map data = {}; + data['monthAverageResult'] = monthAverageResult; + data['monthNumber'] = monthNumber; + data['monthName'] = monthName; + data['yearName'] = yearName; + return data; + } +} + diff --git a/lib/features/health_trackers/models/weight/month_weight_measurement_result_average.dart b/lib/features/health_trackers/models/weight/month_weight_measurement_result_average.dart new file mode 100644 index 0000000..f95470c --- /dev/null +++ b/lib/features/health_trackers/models/weight/month_weight_measurement_result_average.dart @@ -0,0 +1,37 @@ +class MonthWeightMeasurementResultAverage { + dynamic weekfourAverageResult; + dynamic weekthreeAverageResult; + dynamic weektwoAverageResult; + dynamic weekoneAverageResult; + dynamic weekAverageResult; + String? weekDesc; + + MonthWeightMeasurementResultAverage( + {this.weekfourAverageResult, + this.weekthreeAverageResult, + this.weektwoAverageResult, + this.weekoneAverageResult, + this.weekAverageResult, + this.weekDesc}); + + MonthWeightMeasurementResultAverage.fromJson(Map json) { + weekfourAverageResult = json['weekfourAverageResult']; + weekthreeAverageResult = json['weekthreeAverageResult']; + weektwoAverageResult = json['weektwoAverageResult']; + weekoneAverageResult = json['weekoneAverageResult']; + weekAverageResult = json['WeekAverageResult']; + weekDesc = json['WeekDesc']; + } + + Map toJson() { + final Map data = {}; + data['weekfourAverageResult'] = weekfourAverageResult; + data['weekthreeAverageResult'] = weekthreeAverageResult; + data['weektwoAverageResult'] = weektwoAverageResult; + data['weekoneAverageResult'] = weekoneAverageResult; + data['WeekAverageResult'] = weekAverageResult; + data['WeekDesc'] = weekDesc; + return data; + } +} + diff --git a/lib/features/health_trackers/models/weight/week_weight_measurement_result_average.dart b/lib/features/health_trackers/models/weight/week_weight_measurement_result_average.dart new file mode 100644 index 0000000..ad53325 --- /dev/null +++ b/lib/features/health_trackers/models/weight/week_weight_measurement_result_average.dart @@ -0,0 +1,21 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class WeekWeightMeasurementResultAverage { + dynamic dailyAverageResult; + DateTime? weightDate; + + WeekWeightMeasurementResultAverage({this.dailyAverageResult, this.weightDate}); + + WeekWeightMeasurementResultAverage.fromJson(Map json) { + dailyAverageResult = json['DailyAverageResult']; + weightDate = DateUtil.convertStringToDate(json['WeightDate']); + } + + Map toJson() { + final Map data = {}; + data['DailyAverageResult'] = dailyAverageResult; + data['WeightDate'] = weightDate; + return data; + } +} + diff --git a/lib/features/health_trackers/models/weight/weight_measurement_result.dart b/lib/features/health_trackers/models/weight/weight_measurement_result.dart new file mode 100644 index 0000000..896d33f --- /dev/null +++ b/lib/features/health_trackers/models/weight/weight_measurement_result.dart @@ -0,0 +1,77 @@ +import 'dart:developer'; + +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class WeightMeasurementResult { + int? patientID; + int? lineItemNo; + int? weightMeasured; + DateTime? weightDate; + dynamic remark; + bool? isActive; + int? measured; + dynamic unit; + int? chartYear; + dynamic chartMonth; + double? yearAverageResult; + dynamic monthAverageResult; + dynamic weekAverageResult; + dynamic weekDesc; + + WeightMeasurementResult( + {this.patientID, + this.lineItemNo, + this.weightMeasured, + this.weightDate, + this.remark, + this.isActive, + this.measured, + this.unit, + this.chartYear, + this.chartMonth, + this.yearAverageResult, + this.monthAverageResult, + this.weekAverageResult, + this.weekDesc}); + + WeightMeasurementResult.fromJson(Map json) { + try { + patientID = json['PatientID']; + lineItemNo = json['LineItemNo']; + weightMeasured = json['WeightMeasured']; + weightDate = DateUtil.convertStringToDate(json['WeightDate']); + remark = json['Remark']; + isActive = json['IsActive']; + measured = json['Measured']; + unit = json['Unit']; + chartYear = json['ChartYear']; + chartMonth = json['ChartMonth']; + // Convert to double safely since API may return int + yearAverageResult = json['YearAverageResult'] != null ? (json['YearAverageResult'] as num).toDouble() : null; + monthAverageResult = json['MonthAverageResult']; + weekAverageResult = json['WeekAverageResult']; + weekDesc = json['WeekDesc']; + } catch (e) { + log(e.toString()); + } + } + + Map toJson() { + final Map data = {}; + data['PatientID'] = patientID; + data['LineItemNo'] = lineItemNo; + data['WeightMeasured'] = weightMeasured; + data['WeightDate'] = weightDate; + data['Remark'] = remark; + data['IsActive'] = isActive; + data['Measured'] = measured; + data['Unit'] = unit; + data['ChartYear'] = chartYear; + data['ChartMonth'] = chartMonth; + data['YearAverageResult'] = yearAverageResult; + data['MonthAverageResult'] = monthAverageResult; + data['WeekAverageResult'] = weekAverageResult; + data['WeekDesc'] = weekDesc; + return data; + } +} diff --git a/lib/features/health_trackers/models/weight/year_weight_measurement_result_average.dart b/lib/features/health_trackers/models/weight/year_weight_measurement_result_average.dart new file mode 100644 index 0000000..7dc4b24 --- /dev/null +++ b/lib/features/health_trackers/models/weight/year_weight_measurement_result_average.dart @@ -0,0 +1,29 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class YearWeightMeasurementResultAverage { + dynamic monthAverageResult; + int? monthNumber; + String? monthName; + String? yearName; + DateTime? date; + + YearWeightMeasurementResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName}); + + YearWeightMeasurementResultAverage.fromJson(Map json) { + monthAverageResult = json['monthAverageResult']; + monthNumber = json['monthNumber']; + monthName = json['monthName']; + yearName = json['yearName']; + date = DateUtil.getMonthDateTime(monthName!, yearName); + } + + Map toJson() { + final Map data = {}; + data['monthAverageResult'] = monthAverageResult; + data['monthNumber'] = monthNumber; + data['monthName'] = monthName; + data['yearName'] = yearName; + return data; + } +} + diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index 18ffddd..d82712a 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -16,14 +16,19 @@ import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_mode import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; import 'package:hmg_patient_app_new/routes/app_routes.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/services/notification_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class WaterMonitorViewModel extends ChangeNotifier { WaterMonitorRepo waterMonitorRepo; + ErrorHandlerService errorHandlerService; - WaterMonitorViewModel({required this.waterMonitorRepo}); + WaterMonitorViewModel({ + required this.waterMonitorRepo, + required this.errorHandlerService, + }); // Controllers final TextEditingController nameController = TextEditingController(); @@ -190,9 +195,9 @@ class WaterMonitorViewModel extends ChangeNotifier { identificationNo: identification, ); - result.fold((failure) { - log('Error fetching user progress: ${failure.message}'); - }, (apiModel) { + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { log("User Progress Data ($_selectedDuration): ${apiModel.data.toString()}"); // Parse the response based on progress type @@ -300,10 +305,13 @@ class WaterMonitorViewModel extends ChangeNotifier { identificationNo: identification, ); - result.fold((failure) { - _userDetailData = null; - if (onError != null) onError(failure.message); - }, (apiModel) { + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + _userDetailData = null; + if (onError != null) onError(failure.message); + }, + (apiModel) { _userDetailData = apiModel.data; // Populate form fields from the fetched data @@ -322,7 +330,7 @@ class WaterMonitorViewModel extends ChangeNotifier { if (_userDetailData == null) { try { - _navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsScreen); + _navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsPage); } catch (navErr) { log('Navigation to water monitor settings failed: $navErr'); } @@ -686,6 +694,7 @@ class WaterMonitorViewModel extends ChangeNotifier { return result.fold( (failure) { + errorHandlerService.handleError(failure: failure); _validationError = failure.message; _isLoading = false; notifyListeners(); @@ -1060,6 +1069,7 @@ class WaterMonitorViewModel extends ChangeNotifier { return result.fold( (failure) { + errorHandlerService.handleError(failure: failure); log('Error inserting user activity: ${failure.message}'); _isLoading = false; notifyListeners(); @@ -1134,6 +1144,7 @@ class WaterMonitorViewModel extends ChangeNotifier { return result.fold( (failure) { + errorHandlerService.handleError(failure: failure); log('Error undoing user activity: ${failure.message}'); _isLoading = false; notifyListeners(); diff --git a/lib/main.dart b/lib/main.dart index a9d16dc..f0537b3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -173,6 +174,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), 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 eb22ab6..42cba5d 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 @@ -1,4 +1,9 @@ import 'package:flutter/material.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart'; @@ -12,17 +17,12 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart'; -import 'package:provider/provider.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/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.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'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; class HealthCalculatorDetailedPage extends StatefulWidget { HealthCalculatorsTypeEnum calculatorType; @@ -50,8 +50,8 @@ class _HealthCalculatorDetailedPageState extends State months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + const List months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; return months[month - 1]; } @@ -828,7 +846,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier { // expose map-like results for widgets to forward to parent Map? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory}; - Map? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange}; + Map? get caloriesResultMap => + caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange}; Map? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus}; diff --git a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart new file mode 100644 index 0000000..56f82fc --- /dev/null +++ b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart @@ -0,0 +1,567 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.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/presentation/health_trackers/health_trackers_view_model.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'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class AddHealthTrackerEntryPage extends StatefulWidget { + final HealthTrackerTypeEnum trackerType; + + const AddHealthTrackerEntryPage({ + super.key, + required this.trackerType, + }); + + @override + State createState() => _AddHealthTrackerEntryPageState(); +} + +class _AddHealthTrackerEntryPageState extends State { + late DialogService dialogService; + + // Controllers for date and time + final TextEditingController dateController = TextEditingController(); + final TextEditingController timeController = TextEditingController(); + + @override + void initState() { + super.initState(); + dialogService = getIt.get(); + } + + @override + void dispose() { + dateController.dispose(); + timeController.dispose(); + super.dispose(); + } + + /// Get page title based on tracker type + String _getPageTitle() { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return "Add Blood Sugar".needTranslation; + case HealthTrackerTypeEnum.bloodPressure: + return "Add Blood Pressure".needTranslation; + case HealthTrackerTypeEnum.weightTracker: + return "Add Weight".needTranslation; + } + } + + /// Get success message based on tracker type + String _getSuccessMessage() { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return "Blood Sugar Data saved successfully".needTranslation; + case HealthTrackerTypeEnum.bloodPressure: + return "Blood Pressure Data saved successfully".needTranslation; + case HealthTrackerTypeEnum.weightTracker: + return "Weight Data saved successfully".needTranslation; + } + } + + /// Save entry based on tracker type + Future _saveEntry(HealthTrackersViewModel viewModel) async { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + await _saveBloodSugarEntry(viewModel); + break; + case HealthTrackerTypeEnum.bloodPressure: + await _saveBloodPressureEntry(viewModel); + break; + case HealthTrackerTypeEnum.weightTracker: + await _saveWeightEntry(viewModel); + break; + } + } + + // Save Blood Sugar entry + Future _saveBloodSugarEntry(HealthTrackersViewModel viewModel) async { + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + // Combine date and time + final dateTime = "${dateController.text} ${timeController.text}"; + + // Call ViewModel method with callbacks + await viewModel.saveBloodSugarEntry( + dateTime: dateTime, + measureTime: viewModel.selectedBloodSugarMeasureTime, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + _showSuccessAndPop(); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet(message: error); + }, + ); + } + + // Save Weight entry + Future _saveWeightEntry(HealthTrackersViewModel viewModel) async { + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + // Combine date and time + final dateTime = "${dateController.text} ${timeController.text}"; + + // Call ViewModel method with callbacks + await viewModel.saveWeightEntry( + dateTime: dateTime, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + _showSuccessAndPop(); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet(message: error); + }, + ); + } + + // Save Blood Pressure entry + Future _saveBloodPressureEntry(HealthTrackersViewModel viewModel) async { + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + // Combine date and time + final dateTime = "${dateController.text} ${timeController.text}"; + + // Call ViewModel method with callbacks + await viewModel.saveBloodPressureEntry( + dateTime: dateTime, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + _showSuccessAndPop(); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet(message: error); + }, + ); + } + + // Show success message and pop back + void _showSuccessAndPop() { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget( + loadingText: _getSuccessMessage(), + ), + callBackFunc: () { + Navigator.pop(context); + }, + isCloseButtonVisible: false, + isDismissible: true, + isFullScreen: false, + ); + } + + // Reusable method to build selection row widget + Widget _buildSelectionRow({ + required String value, + required String groupValue, + required VoidCallback onTap, + bool useUpperCase = false, + }) { + return SizedBox( + height: 70.h, + child: Row( + spacing: 8.h, + children: [ + Radio( + value: value, + groupValue: groupValue, + activeColor: AppColors.errorColor, + onChanged: (_) => onTap(), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + (useUpperCase ? value.toUpperCase() : value.toCamelCase) + .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .expanded, + ], + ).onPress(onTap), + ); + } + + // Reusable method to show selection bottom sheet + void _showSelectionBottomSheet({ + required BuildContext context, + required String title, + required List items, + required String selectedValue, + required Function(String) onSelected, + bool useUpperCase = false, + }) { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: title.needTranslation, + message: "", + child: Container( + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.7), + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)), + child: ListView.separated( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return _buildSelectionRow( + value: item, + groupValue: selectedValue, + useUpperCase: useUpperCase, + onTap: () { + onSelected(item); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor), + ), + ), + onOkPressed: () {}, + ); + } + + // Blood Sugar unit selection + void _showBloodSugarUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) { + FocusScope.of(context).unfocus(); + _showSelectionBottomSheet( + context: context, + title: "Select Unit".needTranslation, + items: viewModel.bloodSugarUnit, + selectedValue: viewModel.selectedBloodSugarUnit, + onSelected: viewModel.setBloodSugarUnit, + useUpperCase: false, + ); + } + + // Blood Sugar measure time selection + void _showBloodSugarEntryTimeBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) { + FocusScope.of(context).unfocus(); + _showSelectionBottomSheet( + context: context, + title: "Select Measure Time".needTranslation, + items: viewModel.bloodSugarMeasureTimeEnList, + selectedValue: viewModel.selectedBloodSugarMeasureTime, + onSelected: viewModel.setBloodSugarMeasureTime, + useUpperCase: false, + ); + } + + // Weight unit selection + void _showWeightUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) { + FocusScope.of(context).unfocus(); + _showSelectionBottomSheet( + context: context, + title: "Select Unit".needTranslation, + items: viewModel.weightUnits, + selectedValue: viewModel.selectedWeightUnit, + onSelected: viewModel.setWeightUnit, + useUpperCase: false, + ); + } + + // Blood Pressure measured arm selection + void _showMeasuredArmSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) { + FocusScope.of(context).unfocus(); + _showSelectionBottomSheet( + context: context, + title: "Select Arm".needTranslation, + items: viewModel.measuredArmList, + selectedValue: viewModel.selectedMeasuredArm, + onSelected: viewModel.setMeasuredArm, + useUpperCase: false, + ); + } + + // Reusable method to build text field + Widget _buildTextField(TextEditingController controller, String hintText, {TextInputType keyboardType = TextInputType.name}) { + return TextField( + controller: controller, + keyboardType: keyboardType, + maxLines: 1, + cursorHeight: 14.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hintText, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 14.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ); + } + + // Reusable method to build settings row + Widget _buildSettingsRow({ + required String icon, + required String label, + String? value, + Widget? inputField, + String? unit, + VoidCallback? onUnitTap, + VoidCallback? onRowTap, + Color? iconColor, + bool showDivider = true, + }) { + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 40.w, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 10.r, + hasShadow: false, + ), + child: Center(child: Utils.buildSvgWithAssets(icon: icon, height: 22.w, width: 22.w, iconColor: iconColor)), + ), + SizedBox(width: 12.w), + Expanded( + flex: unit != null ? 2 : 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + label.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + if (inputField != null) + Container( + height: 20.w, + alignment: Alignment.centerLeft, + child: inputField, + ) + else if (value != null && value.isNotEmpty) + value.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + ], + ), + ), + if (unit != null) ...[ + Container( + width: 1.w, + height: 30.w, + color: AppColors.dividerColor, + ).paddingOnly(right: 10.w), + Expanded( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + unit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + ], + ), + SizedBox(width: 12.w), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) + ], + ).onPress(onUnitTap ?? () {}), + ), + ] else if (onRowTap != null) ...[ + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down), + SizedBox(width: 8.w), + ], + ], + ).paddingSymmetrical(0.w, 16.w).onPress(onRowTap ?? () {}), + if (showDivider) Divider(height: 1, color: AppColors.dividerColor), + ], + ); + } + + /// Build form fields based on tracker type + Widget _buildFormFields(HealthTrackersViewModel viewModel) { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return _buildBloodSugarForm(viewModel); + case HealthTrackerTypeEnum.bloodPressure: + return _buildBloodPressureForm(viewModel); + case HealthTrackerTypeEnum.weightTracker: + return _buildWeightForm(viewModel); + } + } + + /// Blood Sugar form fields + Widget _buildBloodSugarForm(HealthTrackersViewModel viewModel) { + return Column( + children: [ + _buildSettingsRow( + icon: AppAssets.heightIcon, + label: "Enter Blood Sugar".needTranslation, + inputField: _buildTextField(viewModel.bloodSugarController, '', keyboardType: TextInputType.number), + unit: viewModel.selectedBloodSugarUnit, + onUnitTap: () => _showBloodSugarUnitSelectionBottomSheet(context, viewModel), + ), + _buildDateTimeFields(), + Divider(height: 1, color: AppColors.dividerColor), + _buildSettingsRow( + icon: AppAssets.weight_tracker_icon, + label: "Select Measure Time".needTranslation, + value: viewModel.selectedBloodSugarMeasureTime, + onRowTap: () => _showBloodSugarEntryTimeBottomSheet(context, viewModel), + ), + ], + ); + } + + /// Blood Pressure form fields + Widget _buildBloodPressureForm(HealthTrackersViewModel viewModel) { + return Column( + children: [ + _buildSettingsRow( + icon: AppAssets.bloodPressureIcon, + iconColor: AppColors.greyTextColor, + label: "Enter Systolic Value".needTranslation, + inputField: _buildTextField(viewModel.systolicController, '', keyboardType: TextInputType.number), + ), + _buildSettingsRow( + icon: AppAssets.bloodPressureIcon, + iconColor: AppColors.greyTextColor, + label: "Enter Diastolic Value".needTranslation, + inputField: _buildTextField(viewModel.diastolicController, '', keyboardType: TextInputType.number), + ), + _buildSettingsRow( + icon: AppAssets.bodyIcon, + iconColor: AppColors.greyTextColor, + label: "Select Arm".needTranslation, + value: viewModel.selectedMeasuredArm, + onRowTap: () => _showMeasuredArmSelectionBottomSheet(context, viewModel), + ), + _buildDateTimeFields(), + ], + ); + } + + /// Weight form fields + Widget _buildWeightForm(HealthTrackersViewModel viewModel) { + return Column( + children: [ + _buildSettingsRow( + icon: AppAssets.weightScale, + label: "Enter Weight".needTranslation, + inputField: _buildTextField(viewModel.weightController, '', keyboardType: TextInputType.number), + unit: viewModel.selectedWeightUnit, + onUnitTap: () => _showWeightUnitSelectionBottomSheet(context, viewModel), + ), + _buildDateTimeFields(), + ], + ); + } + + /// Common date and time fields + Widget _buildDateTimeFields() { + return Column( + children: [ + TextInputWidget( + controller: dateController, + isReadOnly: true, + isArrowTrailing: true, + labelText: "Date", + hintText: "Select date".needTranslation, + focusNode: FocusNode(), + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.calendarGrey, + selectionType: SelectionTypeEnum.calendar, + isHideSwitcher: true, + onCalendarTypeChanged: (val) {}, + onChange: (val) { + if (val == null) return; + try { + final parsedDate = DateTime.parse(val); + final formattedDate = DateFormat('dd MMM yyyy').format(parsedDate); + dateController.text = formattedDate; + log("date: $formattedDate"); + } catch (e) { + dateController.text = val; + log("date: $val"); + } + }, + ), + TextInputWidget( + controller: timeController, + isReadOnly: true, + isArrowTrailing: true, + labelText: "Time", + hintText: "Select time".needTranslation, + focusNode: FocusNode(), + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.calendarGrey, + selectionType: SelectionTypeEnum.time, + isHideSwitcher: true, + onCalendarTypeChanged: (val) {}, + onChange: (val) { + if (val == null) return; + timeController.text = val; + log("time: $val"); + }, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final viewModel = context.watch(); + + return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: _getPageTitle(), + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(24.w), + child: CustomButton( + text: "Save".needTranslation, + onPressed: () async => await _saveEntry(viewModel), + borderRadius: 12.r, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ), + child: Container( + margin: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: _buildFormFields(viewModel), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart new file mode 100644 index 0000000..a2b82ec --- /dev/null +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -0,0 +1,1126 @@ +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_export.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/enums.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/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/widgets/tracker_last_value_card.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'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class HealthTrackerDetailPage extends StatefulWidget { + final HealthTrackerTypeEnum trackerType; + + const HealthTrackerDetailPage({super.key, required this.trackerType}); + + @override + State createState() => _HealthTrackerDetailPageState(); +} + +class _HealthTrackerDetailPageState extends State { + @override + void initState() { + super.initState(); + // Load data based on tracker type + WidgetsBinding.instance.addPostFrameCallback((_) async { + final viewModel = context.read(); + await _loadTrackerData(viewModel); + }); + } + + /// Load data based on tracker type + Future _loadTrackerData(HealthTrackersViewModel viewModel) async { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + await viewModel.getBloodSugar(); + break; + case HealthTrackerTypeEnum.bloodPressure: + await viewModel.getBloodPressure(); + break; + case HealthTrackerTypeEnum.weightTracker: + await viewModel.getWeight(); + break; + } + } + + /// Get page title based on tracker type + String _getPageTitle() { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return "Blood Sugar".needTranslation; + case HealthTrackerTypeEnum.bloodPressure: + return "Blood Pressure".needTranslation; + case HealthTrackerTypeEnum.weightTracker: + return "Weight".needTranslation; + } + } + + /// Get unit based on tracker type + String _getUnit() { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return 'mg/dL'; + case HealthTrackerTypeEnum.bloodPressure: + return 'mmHg'; + case HealthTrackerTypeEnum.weightTracker: + return 'kg'; + } + } + + /// Get empty state message based on tracker type + String _getEmptyStateMessage() { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return "Please add data to track your Blood Sugar"; + case HealthTrackerTypeEnum.bloodPressure: + return "Please add data to track your Blood Pressure"; + case HealthTrackerTypeEnum.weightTracker: + return "Please add data to track your Weight"; + } + } + + /// Check if data is empty based on tracker type + bool _hasNoData(HealthTrackersViewModel viewModel) { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return viewModel.weekDiabeticPatientResult.isEmpty && + viewModel.monthDiabeticPatientResult.isEmpty && + viewModel.yearDiabeticPatientResult.isEmpty; + case HealthTrackerTypeEnum.bloodPressure: + return viewModel.weekBloodPressureResult.isEmpty && viewModel.monthBloodPressureResult.isEmpty && viewModel.yearBloodPressureResult.isEmpty; + case HealthTrackerTypeEnum.weightTracker: + return viewModel.weekWeightMeasurementResult.isEmpty && + viewModel.monthWeightMeasurementResult.isEmpty && + viewModel.yearWeightMeasurementResult.isEmpty; + } + } + + // Reusable method to build selection row widget + Widget _buildSelectionRow({ + required String value, + required String groupValue, + required VoidCallback onTap, + bool useUpperCase = false, + }) { + return SizedBox( + height: 70.h, + child: Row( + spacing: 8.h, + children: [ + Radio( + value: value, + groupValue: groupValue, + activeColor: AppColors.errorColor, + onChanged: (_) => onTap(), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + (useUpperCase ? value.toUpperCase() : value.toCamelCase) + .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .expanded, + ], + ).onPress(onTap), + ); + } + + void _showSelectionBottomSheet({ + required BuildContext context, + required String title, + required List items, + required String selectedValue, + required Function(String) onSelected, + bool useUpperCase = false, + }) { + final dialogService = getIt.get(); + + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: title.needTranslation, + message: "", + child: Container( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), + child: ListView.separated( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return _buildSelectionRow( + value: item, + groupValue: selectedValue, + useUpperCase: useUpperCase, + onTap: () { + onSelected(item); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor), + ), + ), + onOkPressed: () {}, + ); + } + + void _showHistoryDurationBottomsheet(BuildContext context, HealthTrackersViewModel viewModel) { + _showSelectionBottomSheet( + context: context, + title: "Select Duration".needTranslation, + items: viewModel.durationFilters, + selectedValue: viewModel.selectedDurationFilter, + onSelected: viewModel.setFilterDuration, + ); + } + + Widget buildHistoryListTile({required String title, required String subTitle, required String measureDesc, double? value}) { + // Get status color and rotation based on value and tracker type + Color statusColor = AppColors.successColor; + double rotation = 0; + + if (value != null) { + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + if (value < 70) { + statusColor = AppColors.errorColor; + rotation = 0; // pointing down + } else if (value <= 100) { + statusColor = AppColors.successColor; + rotation = -3.14159 / 2; // pointing right + } else if (value <= 125) { + statusColor = AppColors.ratingColorYellow; + rotation = 3.14159; // pointing up + } else { + statusColor = AppColors.errorColor; + rotation = 3.14159; // pointing up + } + break; + case HealthTrackerTypeEnum.bloodPressure: + // Systolic pressure ranges + if (value < 90) { + statusColor = AppColors.errorColor; + rotation = 0; // Low - pointing down + } else if (value <= 120) { + statusColor = AppColors.successColor; + rotation = -3.14159 / 2; // Normal - pointing right + } else if (value <= 140) { + statusColor = AppColors.ratingColorYellow; + rotation = 3.14159; // Elevated - pointing up + } else { + statusColor = AppColors.errorColor; + rotation = 3.14159; // High - pointing up + } + break; + case HealthTrackerTypeEnum.weightTracker: + // Weight doesn't have good/bad indicators, just show neutral + statusColor = AppColors.transparent; + rotation = -3.14159 / 2; // pointing right (neutral) + break; + } + } + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + AppCustomChipWidget(labelText: title), + if (measureDesc.isNotEmpty) ...[ + SizedBox(width: 8.w), + AppCustomChipWidget(labelText: measureDesc), + ], + ], + ), + SizedBox(height: 4.h), + subTitle.toText16(weight: FontWeight.w600, color: AppColors.textColor), + ], + ), + Transform.rotate( + angle: rotation, + child: Utils.buildSvgWithAssets( + icon: AppAssets.lowIndicatorIcon, + iconColor: statusColor, + height: 20.h, + width: 20.h, + ), + ), + ], + ).paddingSymmetrical(0, 8.h); + } + + Widget _buildHistoryGraphOrList() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 24.w), + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Consumer(builder: (BuildContext context, HealthTrackersViewModel viewModel, Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + "History".needTranslation.toText16(isBold: true), + if (viewModel.isGraphView) ...[ + SizedBox(width: 12.w), + InkWell( + onTap: () => _showHistoryDurationBottomsheet(context, viewModel), + child: Container( + padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 6.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + backgroundColor: AppColors.greyColor, + borderRadius: 8.r, + hasShadow: true, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500), + SizedBox(width: 4.w), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h), + ], + ), + ), + ), + ], + ], + ), + InkWell( + onTap: () => viewModel.setGraphView(!viewModel.isGraphView), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: ScaleTransition( + scale: animation, + child: child, + ), + ); + }, + child: Container( + key: ValueKey(viewModel.isGraphView), + child: Utils.buildSvgWithAssets( + icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon, + height: 24.h, + width: 24.h, + ), + ), + ), + ), + ], + ), + if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else ...[SizedBox(height: 16.h), _buildHistoryGraph()] + ], + ); + }), + ); + } + + String _formatTime(DateTime time) { + final hour = time.hour; + final minute = time.minute; + final hour12 = hour > 12 ? hour - 12 : (hour == 0 ? 12 : hour); + final period = hour >= 12 ? 'PM' : 'AM'; + return '${hour12.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')} $period'; + } + + String _getDayName(DateTime date) { + const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + return days[date.weekday - 1]; + } + + String _getMonthName(int monthNumber) { + const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + if (monthNumber < 1 || monthNumber > 12) return 'Unknown'; + return months[monthNumber - 1]; + } + + Widget _buildHistoryListView(HealthTrackersViewModel viewModel) { + List listItems = []; + final unit = _getUnit(); + + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + listItems = _buildBloodSugarListItems(viewModel, unit); + break; + case HealthTrackerTypeEnum.bloodPressure: + listItems = _buildBloodPressureListItems(viewModel); + break; + case HealthTrackerTypeEnum.weightTracker: + listItems = _buildWeightListItems(viewModel, unit); + break; + } + + if (viewModel.isLoading) { + return _buildLoadingShimmer().paddingOnly(top: 16.h); + } + + if (listItems.isEmpty) { + return _buildEmptyStateWidget(); + } + + return ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: listItems.length, + separatorBuilder: (context, index) => SizedBox.shrink(), + itemBuilder: (context, index) => listItems[index], + ); + } + + /// Build list items for Blood Sugar + List _buildBloodSugarListItems(HealthTrackersViewModel viewModel, String unit) { + List listItems = []; + final allResults = []; + + allResults.addAll(viewModel.weekDiabeticPatientResult); + allResults.addAll(viewModel.monthDiabeticPatientResult); + allResults.addAll(viewModel.yearDiabeticPatientResult); + + final seenIds = {}; + final uniqueResults = allResults.where((result) { + final id = '${result.lineItemNo}_${result.dateChart?.millisecondsSinceEpoch ?? 0}'; + if (seenIds.contains(id)) return false; + seenIds.add(id); + return true; + }).toList(); + + uniqueResults.sort((a, b) { + final dateA = a.dateChart ?? DateTime(1900); + final dateB = b.dateChart ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + + for (var result in uniqueResults) { + final resultValue = result.resultValue?.toDouble() ?? 0.0; + final value = result.resultValue?.toString() ?? '0'; + final resultUnit = result.unit ?? unit; + final measuredDesc = result.measuredDesc ?? ''; + final date = result.dateChart; + final dateLabel = date != null ? '${_getDayName(date)} ${date.day} ${_getMonthName(date.month).substring(0, 3)}, ${date.year}' : ''; + final timeLabel = date != null ? _formatTime(date) : 'Unknown'; + final displayLabel = date != null ? '$dateLabel, $timeLabel' : 'Unknown'; + final subTitleText = '$value $resultUnit'; + + listItems.add( + Column( + children: [ + buildHistoryListTile( + title: displayLabel, + subTitle: subTitleText, + value: resultValue, + measureDesc: measuredDesc, + ), + Divider(height: 1, color: AppColors.dividerColor).paddingOnly(bottom: 8.h), + ], + ), + ); + } + return listItems; + } + + /// Build list items for Blood Pressure + List _buildBloodPressureListItems(HealthTrackersViewModel viewModel) { + List listItems = []; + final allResults = []; + + allResults.addAll(viewModel.weekBloodPressureResult); + allResults.addAll(viewModel.monthBloodPressureResult); + allResults.addAll(viewModel.yearBloodPressureResult); + + final seenIds = {}; + final uniqueResults = allResults.where((result) { + final id = '${result.lineItemNo}_${result.bloodPressureDate?.millisecondsSinceEpoch ?? 0}'; + if (seenIds.contains(id)) return false; + seenIds.add(id); + return true; + }).toList(); + + uniqueResults.sort((a, b) { + final dateA = a.bloodPressureDate ?? DateTime(1900); + final dateB = b.bloodPressureDate ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + + for (var result in uniqueResults) { + final systolic = result.systolicePressure ?? 0; + final diastolic = result.diastolicPressure ?? 0; + final measuredArmDesc = result.measuredArmDesc ?? ''; + final date = result.bloodPressureDate; + final dateLabel = date != null ? '${_getDayName(date)} ${date.day} ${_getMonthName(date.month).substring(0, 3)}, ${date.year}' : ''; + final timeLabel = date != null ? _formatTime(date) : 'Unknown'; + final displayLabel = date != null ? '$dateLabel, $timeLabel' : 'Unknown'; + final subTitleText = '$systolic/$diastolic mmHg'; + + listItems.add( + Column( + children: [ + buildHistoryListTile( + title: displayLabel, + subTitle: subTitleText, + value: systolic.toDouble(), + measureDesc: measuredArmDesc, + ), + Divider(height: 1, color: AppColors.dividerColor).paddingOnly(bottom: 8.h), + ], + ), + ); + } + return listItems; + } + + /// Build list items for Weight + List _buildWeightListItems(HealthTrackersViewModel viewModel, String unit) { + List listItems = []; + final allResults = []; + + allResults.addAll(viewModel.weekWeightMeasurementResult); + allResults.addAll(viewModel.monthWeightMeasurementResult); + allResults.addAll(viewModel.yearWeightMeasurementResult); + + final seenIds = {}; + final uniqueResults = allResults.where((result) { + final id = '${result.lineItemNo}_${result.weightDate?.millisecondsSinceEpoch ?? 0}'; + if (seenIds.contains(id)) return false; + seenIds.add(id); + return true; + }).toList(); + + uniqueResults.sort((a, b) { + final dateA = a.weightDate ?? DateTime(1900); + final dateB = b.weightDate ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + + for (var result in uniqueResults) { + final weightValue = result.weightMeasured?.toDouble() ?? 0.0; + final weightUnit = result.unit ?? unit; + final date = result.weightDate; + final dateLabel = date != null ? '${_getDayName(date)} ${date.day} ${_getMonthName(date.month).substring(0, 3)}, ${date.year}' : ''; + final timeLabel = date != null ? _formatTime(date) : 'Unknown'; + final displayLabel = date != null ? '$dateLabel, $timeLabel' : 'Unknown'; + final subTitleText = '${weightValue.toInt()} $weightUnit'; + + listItems.add( + Column( + children: [ + buildHistoryListTile( + title: displayLabel, + subTitle: subTitleText, + value: weightValue, + measureDesc: '', + ), + Divider(height: 1, color: AppColors.dividerColor).paddingOnly(bottom: 8.h), + ], + ), + ); + } + return listItems; + } + + Widget _buildLoadingShimmer({bool isForHistory = true}) { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(0.w), + itemCount: 4, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: AppColors.shimmerBaseColor, + highlightColor: AppColors.shimmerHighlightColor, + child: Container( + height: isForHistory ? 60.h : 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + Widget _buildEmptyStateWidget() { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.calendar, + iconColor: AppColors.textColor, + height: 48.w, + width: 48.w, + ), + SizedBox(height: 16.h), + "You do not have any data available.".toText14( + weight: FontWeight.w500, + color: AppColors.textColor, + isCenter: true, + ), + SizedBox(height: 8.h), + _getEmptyStateMessage().toText12( + color: AppColors.greyTextColor, + isCenter: true, + ), + ], + ), + ), + ); + } + + Widget _buildHistoryGraph() { + return Consumer( + builder: (context, viewModel, _) { + final selectedDuration = viewModel.selectedDurationFilter; + List dataPoints = []; + List? secondaryDataPoints; + + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + dataPoints = _buildBloodSugarGraphData(viewModel, selectedDuration); + break; + case HealthTrackerTypeEnum.bloodPressure: + final (systolicData, diastolicData) = _buildBloodPressureGraphData(viewModel, selectedDuration); + dataPoints = systolicData; // Systolic (primary line) + secondaryDataPoints = diastolicData; // Diastolic (secondary line) + break; + case HealthTrackerTypeEnum.weightTracker: + dataPoints = _buildWeightGraphData(viewModel, selectedDuration); + break; + } + + if (dataPoints.isEmpty) { + return _buildEmptyStateWidget(); + } + + if (viewModel.isLoading) { + return Container( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: _buildLoadingShimmer(), + ); + } + + // Calculate max value from both lines for blood pressure + double maxDataValue = dataPoints.isNotEmpty ? dataPoints.map((p) => p.value).reduce((a, b) => a > b ? a : b) : 0.0; + if (secondaryDataPoints != null && secondaryDataPoints.isNotEmpty) { + final secondaryMax = secondaryDataPoints.map((p) => p.value).reduce((a, b) => a > b ? a : b); + if (secondaryMax > maxDataValue) maxDataValue = secondaryMax; + } + + double maxY = maxDataValue > 200 ? (maxDataValue * 1.2) : 250; + double minY = 0; + double horizontalInterval = maxY / 4; + double leftLabelInterval = horizontalInterval; + + // Set colors based on tracker type + Color graphColor = AppColors.successColor; + Color? secondaryGraphColor; + + if (widget.trackerType == HealthTrackerTypeEnum.bloodPressure) { + graphColor = AppColors.errorColor; // Red for Systolic + secondaryGraphColor = AppColors.blueColor; // Blue for Diastolic + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Legend for blood pressure + if (widget.trackerType == HealthTrackerTypeEnum.bloodPressure) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildLegendItem(AppColors.errorColor, "Systolic".needTranslation), + SizedBox(width: 24.w), + _buildLegendItem(AppColors.blueColor, "Diastolic".needTranslation), + ], + ), + SizedBox(height: 12.h), + ], + CustomGraph( + bottomLabelReservedSize: 30, + dataPoints: dataPoints, + secondaryDataPoints: secondaryDataPoints, + makeGraphBasedOnActualValue: false, + leftLabelReservedSize: 50.h, + showGridLines: true, + maxY: maxY, + minY: minY, + showLinePoints: true, + maxX: dataPoints.length > 1 ? dataPoints.length.toDouble() - 0.75 : 1.0, + horizontalInterval: horizontalInterval, + leftLabelInterval: leftLabelInterval, + showShadow: widget.trackerType != HealthTrackerTypeEnum.bloodPressure, + graphColor: graphColor, + secondaryGraphColor: secondaryGraphColor, + graphShadowColor: graphColor.withValues(alpha: 0.15), + getDrawingHorizontalLine: (value) { + if (value % horizontalInterval == 0 && value > 0) { + return FlLine( + color: AppColors.greyTextColor.withValues(alpha: 0.3), + strokeWidth: 1.5, + dashArray: [8, 4], + ); + } + return FlLine(color: AppColors.transparent, strokeWidth: 0); + }, + leftLabelFormatter: (value) { + final interval = maxY / 4; + final positions = [0.0, interval, interval * 2, interval * 3, maxY]; + for (var position in positions) { + if ((value - position).abs() < 1) { + return '${value.toInt()}'.toText10(weight: FontWeight.w600); + } + } + return SizedBox.shrink(); + }, + bottomLabelFormatter: (value, data) { + if (data.isEmpty) return SizedBox.shrink(); + if ((value - value.round()).abs() > 0.01) return SizedBox.shrink(); + int index = value.round(); + if (index < 0 || index >= data.length) return SizedBox.shrink(); + + if (selectedDuration == 'Week' && index < 7) { + return Padding( + padding: EdgeInsets.only(top: 10.h), + child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), + ); + } + if (selectedDuration == 'Month' && index < 6) { + return Padding( + padding: EdgeInsets.only(top: 10.h), + child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), + ); + } + if (selectedDuration == 'Year' && index < 12) { + return Padding( + padding: EdgeInsets.only(top: 10.h), + child: data[index].label.toText8(fontWeight: FontWeight.w600, color: AppColors.labelTextColor), + ); + } + return SizedBox.shrink(); + }, + scrollDirection: selectedDuration == 'Year' ? Axis.horizontal : Axis.vertical, + height: 250.h, + spotColor: graphColor, + ), + ], + ); + }, + ); + } + + /// Build legend item for graph + Widget _buildLegendItem(Color color, String label) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12.w, + height: 12.w, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(2.r), + ), + ), + SizedBox(width: 6.w), + label.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + ], + ); + } + + /// Build graph data for Blood Sugar + List _buildBloodSugarGraphData(HealthTrackersViewModel viewModel, String selectedDuration) { + List dataPoints = []; + final unit = _getUnit(); + + if (selectedDuration == 'Week') { + final weekResults = viewModel.weekDiabeticResultAverage; + if (weekResults.isNotEmpty) { + final sortedResults = List.from(weekResults); + sortedResults.sort((a, b) => (a.dateChart ?? DateTime.now()).compareTo(b.dateChart ?? DateTime.now())); + final last7Days = sortedResults.length > 7 ? sortedResults.sublist(sortedResults.length - 7) : sortedResults; + + for (var result in last7Days) { + final value = result.dailyAverageResult?.toDouble() ?? 0.0; + final date = result.dateChart ?? DateTime.now(); + final label = _getDayName(date).substring(0, 3); + dataPoints.add(DataPoint( + value: value, + label: label, + actualValue: value.toStringAsFixed(1), + time: date, + displayTime: _getDayName(date), + unitOfMeasurement: unit)); + } + } + } else if (selectedDuration == 'Month') { + final monthResults = viewModel.monthDiabeticResultAverage; + if (monthResults.isNotEmpty) { + for (int i = 0; i < monthResults.length; i++) { + final weekData = monthResults[i]; + final value = (weekData.weekAverageResult is num) + ? (weekData.weekAverageResult as num).toDouble() + : double.tryParse(weekData.weekAverageResult?.toString() ?? '0') ?? 0.0; + final weekLabel = weekData.weekDesc ?? 'Week ${i + 1}'; + dataPoints.add(DataPoint( + value: value, + label: 'W${i + 1}', + actualValue: value.toStringAsFixed(1), + time: DateTime.now(), + displayTime: weekLabel, + unitOfMeasurement: unit)); + } + } + } else if (selectedDuration == 'Year') { + final yearResults = viewModel.yearDiabeticResultAverage; + if (yearResults.isNotEmpty) { + for (int targetMonth = 1; targetMonth <= 12; targetMonth++) { + final monthData = yearResults.firstWhere((m) => m.monthNumber == targetMonth, + orElse: () => YearDiabeticResultAverage(monthAverageResult: 0.0, monthNumber: targetMonth, monthName: _getMonthName(targetMonth))); + final value = monthData.monthAverageResult?.toDouble() ?? 0.0; + final monthName = monthData.monthName ?? _getMonthName(targetMonth); + final label = monthName.length >= 3 ? monthName.substring(0, 3) : monthName; + dataPoints.add(DataPoint( + value: value, + label: label, + actualValue: value.toStringAsFixed(1), + time: DateTime(DateTime.now().year, targetMonth, 1), + displayTime: monthName, + unitOfMeasurement: unit)); + } + } + } + return dataPoints; + } + + /// Build graph data for Blood Pressure - returns (systolicData, diastolicData) + (List, List) _buildBloodPressureGraphData(HealthTrackersViewModel viewModel, String selectedDuration) { + List systolicDataPoints = []; + List diastolicDataPoints = []; + const unit = 'mmHg'; + + if (selectedDuration == 'Week') { + final weekResults = viewModel.weekBloodPressureResult; + if (weekResults.isNotEmpty) { + final sortedResults = List.from(weekResults); + sortedResults.sort((a, b) => (a.bloodPressureDate ?? DateTime.now()).compareTo(b.bloodPressureDate ?? DateTime.now())); + final last7Days = sortedResults.length > 7 ? sortedResults.sublist(sortedResults.length - 7) : sortedResults; + + for (var result in last7Days) { + final systolic = (result.systolicePressure ?? 0).toDouble(); + final diastolic = (result.diastolicPressure ?? 0).toDouble(); + final date = result.bloodPressureDate ?? DateTime.now(); + final label = _getDayName(date).substring(0, 3); + + systolicDataPoints.add(DataPoint( + value: systolic, + label: label, + actualValue: '${systolic.toInt()}/${diastolic.toInt()}', + time: date, + displayTime: _getDayName(date), + unitOfMeasurement: unit)); + + diastolicDataPoints.add(DataPoint( + value: diastolic, + label: label, + actualValue: diastolic.toStringAsFixed(0), + time: date, + displayTime: _getDayName(date), + unitOfMeasurement: unit)); + } + } + } else if (selectedDuration == 'Month') { + final monthResults = viewModel.monthBloodPressureResult; + if (monthResults.isNotEmpty) { + // Group by week and calculate averages + final Map> weekGroups = {}; + for (var result in monthResults) { + final weekDesc = result.weekDesc ?? 'Week 1'; + weekGroups.putIfAbsent(weekDesc, () => []); + weekGroups[weekDesc]!.add(result); + } + + int weekIndex = 0; + for (var entry in weekGroups.entries) { + final weekData = entry.value; + double avgSystolic = 0; + double avgDiastolic = 0; + + for (var result in weekData) { + avgSystolic += (result.systolicePressure ?? 0); + avgDiastolic += (result.diastolicPressure ?? 0); + } + + if (weekData.isNotEmpty) { + avgSystolic = avgSystolic / weekData.length; + avgDiastolic = avgDiastolic / weekData.length; + } + + final weekLabel = entry.key; + systolicDataPoints.add(DataPoint( + value: avgSystolic, + label: 'W${weekIndex + 1}', + actualValue: '${avgSystolic.toInt()}/${avgDiastolic.toInt()}', + time: DateTime.now(), + displayTime: weekLabel, + unitOfMeasurement: unit)); + + diastolicDataPoints.add(DataPoint( + value: avgDiastolic, + label: 'W${weekIndex + 1}', + actualValue: avgDiastolic.toStringAsFixed(0), + time: DateTime.now(), + displayTime: weekLabel, + unitOfMeasurement: unit)); + + weekIndex++; + } + } + } else if (selectedDuration == 'Year') { + final yearResults = viewModel.yearBloodPressureResult; + if (yearResults.isNotEmpty) { + // Group by month and calculate averages + final Map> monthGroups = {}; + for (var result in yearResults) { + final chartMonth = result.chartMonth; + final monthNum = _getMonthNumber(chartMonth ?? 'January'); + monthGroups.putIfAbsent(monthNum, () => []); + monthGroups[monthNum]!.add(result); + } + + for (int targetMonth = 1; targetMonth <= 12; targetMonth++) { + double avgSystolic = 0; + double avgDiastolic = 0; + + if (monthGroups.containsKey(targetMonth) && monthGroups[targetMonth]!.isNotEmpty) { + final monthData = monthGroups[targetMonth]!; + for (var result in monthData) { + avgSystolic += (result.systolicePressure ?? 0); + avgDiastolic += (result.diastolicPressure ?? 0); + } + avgSystolic = avgSystolic / monthData.length; + avgDiastolic = avgDiastolic / monthData.length; + } + + final monthName = _getMonthName(targetMonth); + final label = monthName.length >= 3 ? monthName.substring(0, 3) : monthName; + + systolicDataPoints.add(DataPoint( + value: avgSystolic, + label: label, + actualValue: '${avgSystolic.toInt()}/${avgDiastolic.toInt()}', + time: DateTime(DateTime.now().year, targetMonth, 1), + displayTime: monthName, + unitOfMeasurement: unit)); + + diastolicDataPoints.add(DataPoint( + value: avgDiastolic, + label: label, + actualValue: avgDiastolic.toStringAsFixed(0), + time: DateTime(DateTime.now().year, targetMonth, 1), + displayTime: monthName, + unitOfMeasurement: unit)); + } + } + } + return (systolicDataPoints, diastolicDataPoints); + } + + /// Helper to get month number from name + int _getMonthNumber(String monthName) { + const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + final index = months.indexWhere((m) => m.toLowerCase() == monthName.toLowerCase()); + return index >= 0 ? index + 1 : 1; + } + + /// Build graph data for Weight + List _buildWeightGraphData(HealthTrackersViewModel viewModel, String selectedDuration) { + List dataPoints = []; + final unit = _getUnit(); + + if (selectedDuration == 'Week') { + final weekResults = viewModel.weekWeightMeasurementResultAverage; + if (weekResults.isNotEmpty) { + final sortedResults = List.from(weekResults); + sortedResults.sort((a, b) => (a.weightDate ?? DateTime.now()).compareTo(b.weightDate ?? DateTime.now())); + final last7Days = sortedResults.length > 7 ? sortedResults.sublist(sortedResults.length - 7) : sortedResults; + + for (var result in last7Days) { + final value = result.dailyAverageResult?.toDouble() ?? 0.0; + final date = result.weightDate ?? DateTime.now(); + final label = _getDayName(date).substring(0, 3); + dataPoints.add(DataPoint( + value: value, + label: label, + actualValue: value.toStringAsFixed(1), + time: date, + displayTime: _getDayName(date), + unitOfMeasurement: unit)); + } + } + } else if (selectedDuration == 'Month') { + final monthResults = viewModel.monthWeightMeasurementResultAverage; + if (monthResults.isNotEmpty) { + for (int i = 0; i < monthResults.length; i++) { + final weekData = monthResults[i]; + final value = weekData.weekAverageResult?.toDouble() ?? 0.0; + final weekLabel = weekData.weekDesc ?? 'Week ${i + 1}'; + dataPoints.add(DataPoint( + value: value, + label: 'W${i + 1}', + actualValue: value.toStringAsFixed(1), + time: DateTime.now(), + displayTime: weekLabel, + unitOfMeasurement: unit)); + } + } + } else if (selectedDuration == 'Year') { + final yearResults = viewModel.yearWeightMeasurementResultAverage; + if (yearResults.isNotEmpty) { + for (int targetMonth = 1; targetMonth <= 12; targetMonth++) { + final monthData = yearResults.firstWhere((m) => m.monthNumber == targetMonth, + orElse: () => + YearWeightMeasurementResultAverage(monthAverageResult: 0.0, monthNumber: targetMonth, monthName: _getMonthName(targetMonth))); + final value = monthData.monthAverageResult?.toDouble() ?? 0.0; + final monthName = monthData.monthName ?? _getMonthName(targetMonth); + final label = monthName.length >= 3 ? monthName.substring(0, 3) : monthName; + dataPoints.add(DataPoint( + value: value, + label: label, + actualValue: value.toStringAsFixed(1), + time: DateTime(DateTime.now().year, targetMonth, 1), + displayTime: monthName, + unitOfMeasurement: unit)); + } + } + } + return dataPoints; + } + + void onSendEmailPressed(BuildContext context) async { + // TODO: Implement send email functionality + } + + Widget _buildPageShimmer() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + children: [ + SizedBox(height: 16.h), + Shimmer.fromColors( + baseColor: AppColors.shimmerBaseColor, + highlightColor: AppColors.shimmerHighlightColor, + child: Container( + height: 120.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(24.r), + ), + ), + ), + SizedBox(height: 16.h), + Shimmer.fromColors( + baseColor: AppColors.shimmerBaseColor, + highlightColor: AppColors.shimmerHighlightColor, + child: Container( + height: 300.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(24.r), + ), + ), + ), + SizedBox(height: 16.h), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + sendEmail: () async => onSendEmailPressed(context), + title: _getPageTitle(), + bottomChild: Consumer( + builder: (context, viewModel, child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(24.w), + child: CustomButton( + text: "Add new Record".needTranslation, + onPressed: () { + if (!viewModel.isLoading) { + context.navigateWithName(AppRoutes.addHealthTrackerEntryPage, arguments: widget.trackerType); + } + }, + icon: AppAssets.add_icon, + borderRadius: 12.r, + borderColor: AppColors.transparent, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + }, + ), + child: Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading) { + return _buildPageShimmer(); + } + + if (_hasNoData(viewModel)) { + return _buildEmptyStateWidget(); + } + + return Column( + children: [ + SizedBox(height: 16.h), + TrackerLastValueCard(trackerType: widget.trackerType), + SizedBox(height: 16.h), + _buildHistoryGraphOrList(), + SizedBox(height: 16.h), + ], + ); + }, + ), + ), + ); + } +} diff --git a/lib/presentation/health_trackers/health_trackers_page.dart b/lib/presentation/health_trackers/health_trackers_page.dart new file mode 100644 index 0000000..c55e8b4 --- /dev/null +++ b/lib/presentation/health_trackers/health_trackers_page.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/enums.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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +class HealthTrackersPage extends StatefulWidget { + const HealthTrackersPage({super.key}); + + @override + State createState() => _HealthTrackersPageState(); +} + +Widget buildHealthTrackerCard({ + required String icon, + required String title, + required String description, + required Color iconBgColor, + required VoidCallback onTap, +}) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: iconBgColor, borderRadius: 10.r), + height: 40.w, + width: 40.w, + child: Utils.buildSvgWithAssets( + icon: icon, + fit: BoxFit.none, + height: 22.w, + width: 22.w, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title.toText16(weight: FontWeight.w600), + description.toText12( + fontWeight: FontWeight.w500, + color: Color(0xFF8F9AA3), + ), + ], + ), + ), + SizedBox(width: 12.w), + Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor, + ), + ], + ).paddingAll(16.w), + ).onPress(onTap); +} + +class _HealthTrackersPageState extends State { + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Health Trackers".needTranslation, + child: Column( + children: [ + buildHealthTrackerCard( + iconBgColor: AppColors.primaryRedColor, + icon: AppAssets.bloodSugarOnlyIcon, + title: "Blood Sugar".needTranslation, + description: "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation, + onTap: () { + context.navigateWithName( + AppRoutes.healthTrackerDetailPage, + arguments: HealthTrackerTypeEnum.bloodSugar, + ); + }, + ), + SizedBox(height: 16.h), + buildHealthTrackerCard( + iconBgColor: AppColors.infoColor, + icon: AppAssets.bloodPressureIcon, + title: "Blood Pressure".needTranslation, + description: "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.".needTranslation, + onTap: () { + context.navigateWithName( + AppRoutes.healthTrackerDetailPage, + arguments: HealthTrackerTypeEnum.bloodPressure, + ); + }, + ), + SizedBox(height: 16.h), + buildHealthTrackerCard( + iconBgColor: AppColors.successColor, + icon: AppAssets.weightIcon, + title: "Weight".needTranslation, + description: "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.".needTranslation, + onTap: () { + context.navigateWithName( + AppRoutes.healthTrackerDetailPage, + arguments: HealthTrackerTypeEnum.weightTracker, + ); + }, + ), + ], + ).paddingSymmetrical(20.w, 24.h), + ); + } +} diff --git a/lib/presentation/health_trackers/health_trackers_view_model.dart b/lib/presentation/health_trackers/health_trackers_view_model.dart new file mode 100644 index 0000000..059c11e --- /dev/null +++ b/lib/presentation/health_trackers/health_trackers_view_model.dart @@ -0,0 +1,1012 @@ +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/blood_pressure_result.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/diabetic_patient_result.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/month_diabetic_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/month_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/weight_measurement_result.dart'; +import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class HealthTrackersViewModel extends ChangeNotifier { + HealthTrackersRepo healthTrackersRepo; + ErrorHandlerService errorHandlerService; + + HealthTrackersViewModel({required this.healthTrackersRepo, required this.errorHandlerService}); + + // ==================== STATE MANAGEMENT ==================== + bool isLoading = false; + String? _errorMessage; + + String? get errorMessage => _errorMessage; + + List get durationFilters => ["Week", "Month", "Year"]; + + String _selectedDuration = "Week"; + bool _isGraphView = true; + + String get selectedDurationFilter => _selectedDuration; + + bool get isGraphView => _isGraphView; + + final List bloodSugarUnit = ['mg/dlt', 'mol/L']; + + String _selectedBloodSugarUnit = 'mg/dlt'; + + String get selectedBloodSugarUnit => _selectedBloodSugarUnit; + + String _selectedBloodSugarMeasureTime = ''; + + String get selectedBloodSugarMeasureTime => _selectedBloodSugarMeasureTime; + + final List bloodSugarMeasureTimeEnList = [ + 'Before Breakfast', + 'After Breakfast', + 'Before Lunch', + 'After Lunch', + 'Before Dinner', + 'After Dinner', + 'Before Sleep', + 'After Sleep', + 'Fasting', + 'Other', + ]; + final List bloodSugarMeasureTimeArList = [ + "قبل الإفطار", + "بعد الإفطار", + "قبل الغداء", + "بعد الغداء", + "قبل العشاء", + "بعد العشاء", + "قبل النوم", + "بعد النوم", + "صائم", + "آخر", + ]; + + // Setters with notification + void setBloodSugarMeasureTime(String duration) async { + _selectedBloodSugarMeasureTime = duration; + notifyListeners(); + } + + // Setters with notification + void setFilterDuration(String duration) async { + _selectedDuration = duration; + notifyListeners(); + } + + // Setters with notification + void setGraphView(bool value) { + _isGraphView = value; + notifyListeners(); + } + + void setBloodSugarUnit(String unit) { + _selectedBloodSugarUnit = unit; + notifyListeners(); + } + + // ==================== WEIGHT FORM FIELDS ==================== + final List weightUnits = ['kg', 'lb']; + String _selectedWeightUnit = 'kg'; + + String get selectedWeightUnit => _selectedWeightUnit; + + void setWeightUnit(String unit) { + _selectedWeightUnit = unit; + notifyListeners(); + } + + // ==================== BLOOD PRESSURE FORM FIELDS ==================== + final List measuredArmList = ['Left Arm', 'Right Arm']; + String _selectedMeasuredArm = ''; + + String get selectedMeasuredArm => _selectedMeasuredArm; + + void setMeasuredArm(String arm) { + _selectedMeasuredArm = arm; + notifyListeners(); + } + + // Text Controllers + TextEditingController weightController = TextEditingController(); + TextEditingController bloodSugarController = TextEditingController(); + TextEditingController systolicController = TextEditingController(); + TextEditingController diastolicController = TextEditingController(); + + // Get current progress list based on selected duration + // dynamic get currentProgressData { + // switch (_selectedDuration) { + // case 'Daily': + // return _todayProgressList; + // case 'Weekly': + // return _weekProgressList; + // case 'Monthly': + // return _monthProgressList; + // default: + // return _todayProgressList; + // } + // } + + // ==================== WEIGHT TRACKING DATA ==================== + final List _monthWeightMeasurementResultAverage = []; + final List _weekWeightMeasurementResultAverage = []; + final List _yearWeightMeasurementResultAverage = []; + + final List _monthWeightMeasurementResult = []; + final List _weekWeightMeasurementResult = []; + final List _yearWeightMeasurementResult = []; + + // Getters for weight data + List get monthWeightMeasurementResultAverage => _monthWeightMeasurementResultAverage; + + List get weekWeightMeasurementResultAverage => _weekWeightMeasurementResultAverage; + + List get yearWeightMeasurementResultAverage => _yearWeightMeasurementResultAverage; + + List get monthWeightMeasurementResult => _monthWeightMeasurementResult; + + List get weekWeightMeasurementResult => _weekWeightMeasurementResult; + + List get yearWeightMeasurementResult => _yearWeightMeasurementResult; + + // ==================== BLOOD PRESSURE TRACKING DATA ==================== + final List _monthBloodPressureResultAverage = []; + final List _weekBloodPressureResultAverage = []; + final List _yearBloodPressureResultAverage = []; + + final List _monthBloodPressureResult = []; + final List _weekBloodPressureResult = []; + final List _yearBloodPressureResult = []; + + // Getters for blood pressure data + List get monthBloodPressureResultAverage => _monthBloodPressureResultAverage; + + List get weekBloodPressureResultAverage => _weekBloodPressureResultAverage; + + List get yearBloodPressureResultAverage => _yearBloodPressureResultAverage; + + List get monthBloodPressureResult => _monthBloodPressureResult; + + List get weekBloodPressureResult => _weekBloodPressureResult; + + List get yearBloodPressureResult => _yearBloodPressureResult; + + // ==================== BLOOD SUGAR (DIABETIC) TRACKING DATA ==================== + final List _monthDiabeticResultAverage = []; + final List _weekDiabeticResultAverage = []; + final List _yearDiabeticResultAverage = []; + + final List _monthDiabeticPatientResult = []; + final List _weekDiabeticPatientResult = []; + final List _yearDiabeticPatientResult = []; + + // Getters for blood sugar data + List get monthDiabeticResultAverage => _monthDiabeticResultAverage; + + List get weekDiabeticResultAverage => _weekDiabeticResultAverage; + + List get yearDiabeticResultAverage => _yearDiabeticResultAverage; + + List get monthDiabeticPatientResult => _monthDiabeticPatientResult; + + List get weekDiabeticPatientResult => _weekDiabeticPatientResult; + + List get yearDiabeticPatientResult => _yearDiabeticPatientResult; + + // ==================== WEIGHT TRACKING METHODS ==================== + + /// Fetch weight averages and results + Future getWeight() async { + isLoading = true; + notifyListeners(); + + try { + // Fetch weight averages + final averageResult = await healthTrackersRepo.getWeightMeasurementResultAverage(); + + averageResult.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + if (data is Map) { + // Clear existing data + _monthWeightMeasurementResultAverage.clear(); + _weekWeightMeasurementResultAverage.clear(); + _yearWeightMeasurementResultAverage.clear(); + + // Parse month averages + if (data['monthAverageList'] != null) { + for (var item in (data['monthAverageList'] as List)) { + _monthWeightMeasurementResultAverage.add( + MonthWeightMeasurementResultAverage.fromJson(item), + ); + } + } + + // Parse week averages + if (data['weekAverageList'] != null) { + for (var item in (data['weekAverageList'] as List)) { + _weekWeightMeasurementResultAverage.add( + WeekWeightMeasurementResultAverage.fromJson(item), + ); + } + } + + // Parse year averages + if (data['yearAverageList'] != null) { + for (var item in (data['yearAverageList'] as List)) { + _yearWeightMeasurementResultAverage.add( + YearWeightMeasurementResultAverage.fromJson(item), + ); + } + } + } + }, + ); + + // Fetch weight results + final resultsResponse = await healthTrackersRepo.getWeightMeasurementResults(); + + resultsResponse.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + if (data is Map) { + // Clear existing data + _monthWeightMeasurementResult.clear(); + _weekWeightMeasurementResult.clear(); + _yearWeightMeasurementResult.clear(); + + // Parse week results + if (data['weekResultList'] != null) { + for (var item in (data['weekResultList'] as List)) { + _weekWeightMeasurementResult.add(WeightMeasurementResult.fromJson(item)); + } + } + + // Parse month results + if (data['monthResultList'] != null) { + for (var item in (data['monthResultList'] as List)) { + _monthWeightMeasurementResult.add(WeightMeasurementResult.fromJson(item)); + } + } + + // Parse year results + if (data['yearResultList'] != null) { + for (var item in (data['yearResultList'] as List)) { + _yearWeightMeasurementResult.add(WeightMeasurementResult.fromJson(item)); + } + } + } + }, + ); + } catch (e) { + log('Error in getWeight: $e'); + } finally { + isLoading = false; + notifyListeners(); + } + } + + /// Add new weight result + Future addWeightResult({ + required String weightDate, + required String weightMeasured, + required int weightUnit, + }) async { + try { + final result = await healthTrackersRepo.addWeightMeasurementResult( + weightDate: weightDate, + weightMeasured: weightMeasured, + weightUnit: weightUnit, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful add + await getWeight(); + }, + ); + + return success; + } catch (e) { + log('Error in addWeightResult: $e'); + return false; + } + } + + /// Update existing weight result + Future updateWeightResult({ + required int lineItemNo, + required int weightUnit, + required String weightMeasured, + required String weightDate, + }) async { + try { + final result = await healthTrackersRepo.updateWeightMeasurementResult( + lineItemNo: lineItemNo, + weightUnit: weightUnit, + weightMeasured: weightMeasured, + weightDate: weightDate, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful update + await getWeight(); + }, + ); + + return success; + } catch (e) { + log('Error in updateWeightResult: $e'); + return false; + } + } + + /// Delete weight result + Future deleteWeightResult({ + required int lineItemNo, + }) async { + try { + final result = await healthTrackersRepo.deactivateWeightMeasurementStatus( + lineItemNo: lineItemNo, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful delete + await getWeight(); + }, + ); + + return success; + } catch (e) { + return false; + } + } + + // ==================== BLOOD PRESSURE TRACKING METHODS ==================== + + /// Fetch blood pressure averages and results + Future getBloodPressure() async { + isLoading = true; + notifyListeners(); + + try { + // Fetch blood pressure averages + final averageResult = await healthTrackersRepo.getBloodPressureResultAverage(); + + averageResult.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + + if (data is Map) { + // Clear existing data + _monthBloodPressureResultAverage.clear(); + _weekBloodPressureResultAverage.clear(); + _yearBloodPressureResultAverage.clear(); + + // Parse month averages + if (data['monthList'] != null) { + for (var item in (data['monthList'] as List)) { + _monthBloodPressureResultAverage.add( + MonthBloodPressureResultAverage.fromJson(item), + ); + } + } + + // Parse week averages + if (data['weekList'] != null) { + for (var item in (data['weekList'] as List)) { + _weekBloodPressureResultAverage.add( + WeekBloodPressureResultAverage.fromJson(item), + ); + } + } + + // Parse year averages + if (data['yearList'] != null) { + for (var item in (data['yearList'] as List)) { + _yearBloodPressureResultAverage.add( + YearBloodPressureResultAverage.fromJson(item), + ); + } + } + } + }, + ); + + // Fetch blood pressure results + final resultsResponse = await healthTrackersRepo.getBloodPressureResults(); + + resultsResponse.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + if (data is Map) { + // Clear existing data + _monthBloodPressureResult.clear(); + _weekBloodPressureResult.clear(); + _yearBloodPressureResult.clear(); + + // Parse week results + if (data['weekList'] != null) { + for (var item in (data['weekList'] as List)) { + _weekBloodPressureResult.add(BloodPressureResult.fromJson(item)); + } + } + + // Parse month results + if (data['monthList'] != null) { + for (var item in (data['monthList'] as List)) { + _monthBloodPressureResult.add(BloodPressureResult.fromJson(item)); + } + } + + // Parse year results + if (data['yearList'] != null) { + for (var item in (data['yearList'] as List)) { + _yearBloodPressureResult.add(BloodPressureResult.fromJson(item)); + } + } + } + }, + ); + } catch (e) { + log('Error in getBloodPressure: $e'); + } finally { + isLoading = false; + notifyListeners(); + } + } + + /// Add or Update blood pressure result + Future addOrUpdateBloodPressureResult({ + required String bloodPressureDate, + required String diastolicPressure, + required String systolicePressure, + required int measuredArm, + int? lineItemNo, + bool isUpdate = false, + }) async { + try { + final result = isUpdate + ? await healthTrackersRepo.updateBloodPressureResult( + bloodPressureDate: bloodPressureDate, + diastolicPressure: diastolicPressure, + systolicePressure: systolicePressure, + measuredArm: measuredArm, + lineItemNo: lineItemNo!, + ) + : await healthTrackersRepo.addBloodPressureResult( + bloodPressureDate: bloodPressureDate, + diastolicPressure: diastolicPressure, + systolicePressure: systolicePressure, + measuredArm: measuredArm, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful add/update + await getBloodPressure(); + }, + ); + + return success; + } catch (e) { + log('Error in addOrUpdateBloodPressureResult: $e'); + return false; + } + } + + /// Delete blood pressure result + Future deleteBloodPressureResult({ + required int lineItemNo, + }) async { + try { + final result = await healthTrackersRepo.deactivateBloodPressureStatus( + lineItemNo: lineItemNo, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful delete + await getBloodPressure(); + }, + ); + + return success; + } catch (e) { + log('Error in deleteBloodPressureResult: $e'); + return false; + } + } + + // ==================== BLOOD SUGAR (DIABETIC) TRACKING METHODS ==================== + + /// Fetch blood sugar averages and results + Future getBloodSugar() async { + isLoading = true; + notifyListeners(); + + try { + // Fetch blood sugar averages + final averageResult = await healthTrackersRepo.getDiabeticResultAverage(); + + averageResult.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + if (data is Map) { + // Clear existing data + _monthDiabeticResultAverage.clear(); + _weekDiabeticResultAverage.clear(); + _yearDiabeticResultAverage.clear(); + + // Parse month averages + if (data['monthAverageList'] != null) { + for (var item in (data['monthAverageList'] as List)) { + _monthDiabeticResultAverage.add( + MonthDiabeticResultAverage.fromJson(item), + ); + } + } + + // Parse week averages + if (data['weekAverageList'] != null) { + for (var item in (data['weekAverageList'] as List)) { + _weekDiabeticResultAverage.add( + WeekDiabeticResultAverage.fromJson(item), + ); + } + } + + // Parse year averages + if (data['yearAverageList'] != null) { + for (var item in (data['yearAverageList'] as List)) { + _yearDiabeticResultAverage.add( + YearDiabeticResultAverage.fromJson(item), + ); + } + } + } + }, + ); + + // Fetch blood sugar results + final resultsResponse = await healthTrackersRepo.getDiabeticResults(); + + resultsResponse.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) { + final data = apiModel.data; + if (data is Map) { + // Clear existing data + _monthDiabeticPatientResult.clear(); + _weekDiabeticPatientResult.clear(); + _yearDiabeticPatientResult.clear(); + + // Parse week results + if (data['weekResultList'] != null) { + for (var item in (data['weekResultList'] as List)) { + _weekDiabeticPatientResult.add(DiabeticPatientResult.fromJson(item)); + } + } + + // Parse month results + if (data['monthResultList'] != null) { + for (var item in (data['monthResultList'] as List)) { + _monthDiabeticPatientResult.add(DiabeticPatientResult.fromJson(item)); + } + } + + // Parse year results + if (data['yearResultList'] != null) { + for (var item in (data['yearResultList'] as List)) { + _yearDiabeticPatientResult.add(DiabeticPatientResult.fromJson(item)); + } + } + } + }, + ); + } catch (e) { + log('Error in getBloodSugar: $e'); + } finally { + isLoading = false; + notifyListeners(); + } + } + + /// Add new blood sugar result + Future addBloodSugarResult({ + required String bloodSugarDateChart, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + }) async { + try { + final result = await healthTrackersRepo.addDiabeticResult( + bloodSugarDateChart: bloodSugarDateChart, + bloodSugarResult: bloodSugarResult, + diabeticUnit: diabeticUnit, + measuredTime: measuredTime, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful add + await getBloodSugar(); + }, + ); + + return success; + } catch (e) { + log('Error in addBloodSugarResult: $e'); + return false; + } + } + + /// Update existing blood sugar result + Future updateBloodSugarResult({ + required DateTime month, + required DateTime hour, + required String bloodSugarResult, + required String diabeticUnit, + required int measuredTime, + required int lineItemNo, + }) async { + try { + final result = await healthTrackersRepo.updateDiabeticResult( + month: month, + hour: hour, + bloodSugarResult: bloodSugarResult, + diabeticUnit: diabeticUnit, + measuredTime: measuredTime, + lineItemNo: lineItemNo, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful update + await getBloodSugar(); + }, + ); + + return success; + } catch (e) { + log('Error in updateBloodSugarResult: $e'); + return false; + } + } + + /// Delete blood sugar result + Future deleteBloodSugarResult({ + required int lineItemNo, + }) async { + try { + final result = await healthTrackersRepo.deactivateDiabeticStatus( + lineItemNo: lineItemNo, + ); + + bool success = false; + + result.fold( + (failure) => errorHandlerService.handleError(failure: failure), + (apiModel) async { + success = true; + // Refresh data after successful delete + await getBloodSugar(); + }, + ); + + return success; + } catch (e) { + log('Error in deleteBloodSugarResult: $e'); + return false; + } + } + + // Validation method + String? _validateBloodSugarEntry(String dateTime) { + // Validate blood sugar value + if (bloodSugarController.text.trim().isEmpty) { + return "Please enter blood sugar value"; + } + + final bloodSugarValue = double.tryParse(bloodSugarController.text.trim()); + if (bloodSugarValue == null) { + return "Please enter a valid number"; + } + + if (bloodSugarValue <= 0) { + return "Blood sugar value must be greater than 0"; + } + + // Validate reasonable range (typical ranges) + if (bloodSugarValue > 1000) { + return "Blood sugar value seems too high. Please check and enter again"; + } + + // Validate date time + if (dateTime.trim().isEmpty) { + return "Please select date and time"; + } + + // Validate measure time + if (_selectedBloodSugarMeasureTime.isEmpty) { + return "Please select when the measurement was taken"; + } + + return null; // No errors + } + + // Save blood sugar entry with validation + Future saveBloodSugarEntry({ + required String dateTime, + required String measureTime, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + // Validate + final validationError = _validateBloodSugarEntry(dateTime); + if (validationError != null) { + _errorMessage = validationError; + if (onFailure != null) onFailure(validationError); + return; + } + + // Clear previous error and show loading + _errorMessage = null; + isLoading = true; + notifyListeners(); + + try { + // Get measure time index (0-based, but API expects 1-based) + final measureTimeIndex = bloodSugarMeasureTimeEnList.indexOf(measureTime); + + // Call API + final success = await addBloodSugarResult( + bloodSugarDateChart: dateTime, + bloodSugarResult: bloodSugarController.text.trim(), + diabeticUnit: _selectedBloodSugarUnit, + measuredTime: measureTimeIndex >= 0 ? measureTimeIndex : 0, + ); + + isLoading = false; + + if (success) { + // Clear form after successful save + bloodSugarController.clear(); + _selectedBloodSugarMeasureTime = ''; + notifyListeners(); + if (onSuccess != null) onSuccess(); + } else { + _errorMessage = "Failed to save blood sugar entry. Please try again"; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } catch (e) { + log('Error in saveBloodSugarEntry: $e'); + _errorMessage = "An error occurred. Please try again"; + isLoading = false; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } + + // ==================== WEIGHT ENTRY METHODS ==================== + + // Validate weight entry before saving + String? _validateWeightEntry(String dateTime) { + // Validate weight value + final weightValue = weightController.text.trim(); + if (weightValue.isEmpty) { + return "Please enter weight value"; + } + + // Check if it's a valid number + final parsedValue = double.tryParse(weightValue); + if (parsedValue == null || parsedValue <= 0) { + return "Please enter a valid weight value"; + } + + // Validate date time + if (dateTime.trim().isEmpty) { + return "Please select date and time"; + } + + return null; // No errors + } + + // Save weight entry with validation + Future saveWeightEntry({ + required String dateTime, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + // Validate + final validationError = _validateWeightEntry(dateTime); + if (validationError != null) { + _errorMessage = validationError; + if (onFailure != null) onFailure(validationError); + return; + } + + // Clear previous error and show loading + _errorMessage = null; + isLoading = true; + notifyListeners(); + + try { + // Get weight unit index (0 = kg, 1 = lb) + final weightUnitIndex = weightUnits.indexOf(_selectedWeightUnit); + + // Call API + final success = await addWeightResult( + weightDate: dateTime, + weightMeasured: weightController.text.trim(), + weightUnit: weightUnitIndex >= 0 ? weightUnitIndex : 0, + ); + + isLoading = false; + + if (success) { + // Clear form after successful save + weightController.clear(); + notifyListeners(); + if (onSuccess != null) onSuccess(); + } else { + _errorMessage = "Failed to save weight entry. Please try again"; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } catch (e) { + log('Error in saveWeightEntry: $e'); + _errorMessage = "An error occurred. Please try again"; + isLoading = false; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } + + // ==================== BLOOD PRESSURE ENTRY METHODS ==================== + + // Validate blood pressure entry before saving + String? _validateBloodPressureEntry(String dateTime) { + // Validate systolic value + final systolicValue = systolicController.text.trim(); + if (systolicValue.isEmpty) { + return "Please enter systolic value"; + } + final parsedSystolic = int.tryParse(systolicValue); + if (parsedSystolic == null || parsedSystolic <= 0) { + return "Please enter a valid systolic value"; + } + + // Validate diastolic value + final diastolicValue = diastolicController.text.trim(); + if (diastolicValue.isEmpty) { + return "Please enter diastolic value"; + } + final parsedDiastolic = int.tryParse(diastolicValue); + if (parsedDiastolic == null || parsedDiastolic <= 0) { + return "Please enter a valid diastolic value"; + } + + // Validate arm selection + if (_selectedMeasuredArm.isEmpty) { + return "Please select measured arm"; + } + + // Validate date time + if (dateTime.trim().isEmpty) { + return "Please select date and time"; + } + + return null; // No errors + } + + // Save blood pressure entry with validation + Future saveBloodPressureEntry({ + required String dateTime, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + // Validate + final validationError = _validateBloodPressureEntry(dateTime); + if (validationError != null) { + _errorMessage = validationError; + if (onFailure != null) onFailure(validationError); + return; + } + + // Clear previous error and show loading + _errorMessage = null; + isLoading = true; + notifyListeners(); + + try { + // Get measured arm index (0 = Left Arm, 1 = Right Arm) + final measuredArmIndex = measuredArmList.indexOf(_selectedMeasuredArm); + + // Call API + final success = await addOrUpdateBloodPressureResult( + bloodPressureDate: dateTime, + systolicePressure: systolicController.text.trim(), + diastolicPressure: diastolicController.text.trim(), + measuredArm: measuredArmIndex >= 0 ? measuredArmIndex : 0, + isUpdate: false, + ); + + isLoading = false; + + if (success) { + // Clear form after successful save + systolicController.clear(); + diastolicController.clear(); + _selectedMeasuredArm = ''; + notifyListeners(); + if (onSuccess != null) onSuccess(); + } else { + _errorMessage = "Failed to save blood pressure entry. Please try again"; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } catch (e) { + log('Error in saveBloodPressureEntry: $e'); + _errorMessage = "An error occurred. Please try again"; + isLoading = false; + notifyListeners(); + if (onFailure != null) onFailure(_errorMessage!); + } + } + + @override + void dispose() { + bloodSugarController.dispose(); + weightController.dispose(); + systolicController.dispose(); + diastolicController.dispose(); + super.dispose(); + } +} diff --git a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart new file mode 100644 index 0000000..542baa5 --- /dev/null +++ b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart @@ -0,0 +1,271 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/enums.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/presentation/health_trackers/health_trackers_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class TrackerLastValueCard extends StatelessWidget { + final HealthTrackerTypeEnum trackerType; + + const TrackerLastValueCard({super.key, required this.trackerType}); + + /// Get status text and color based on blood sugar value + (String status, Color color, Color bgColor) _getBloodSugarStatus(double value) { + if (value < 70) { + return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); + } else if (value <= 100) { + return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + } else if (value <= 125) { + return ('Pre-diabetic'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); + } else { + return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); + } + } + + /// Get status text and color based on blood pressure value (systolic) + (String status, Color color, Color bgColor) _getBloodPressureStatus(int systolic) { + if (systolic < 90) { + return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); + } else if (systolic <= 120) { + return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + } else if (systolic <= 140) { + return ('Elevated'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); + } else { + return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); + } + } + + /// Get status for weight (neutral - no good/bad status) + (String status, Color color, Color bgColor) _getWeightStatus() { + return ('Recorded'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + } + + /// Get default unit based on tracker type + String _getDefaultUnit() { + switch (trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + return 'mg/dL'; + case HealthTrackerTypeEnum.bloodPressure: + return 'mmHg'; + case HealthTrackerTypeEnum.weightTracker: + return 'kg'; + } + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, viewModel, child) { + // Get the last record based on tracker type + dynamic lastRecord; + String displayValue = '--'; + String unit = _getDefaultUnit(); + DateTime? lastDate; + String status = ''; + Color statusColor = AppColors.greyTextColor; + + switch (trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + final allResults = [ + ...viewModel.weekDiabeticPatientResult, + ...viewModel.monthDiabeticPatientResult, + ...viewModel.yearDiabeticPatientResult, + ]; + if (allResults.isNotEmpty) { + allResults.sort((a, b) { + final dateA = a.dateChart ?? DateTime(1900); + final dateB = b.dateChart ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + lastRecord = allResults.first; + final lastValue = lastRecord.resultValue?.toDouble() ?? 0.0; + displayValue = lastValue.toStringAsFixed(0); + unit = lastRecord.unit ?? 'mg/dL'; + lastDate = lastRecord.dateChart; + final (s, c, _) = _getBloodSugarStatus(lastValue); + status = s; + statusColor = c; + } + break; + + case HealthTrackerTypeEnum.bloodPressure: + final allResults = [ + ...viewModel.weekBloodPressureResult, + ...viewModel.monthBloodPressureResult, + ...viewModel.yearBloodPressureResult, + ]; + if (allResults.isNotEmpty) { + allResults.sort((a, b) { + final dateA = a.bloodPressureDate ?? DateTime(1900); + final dateB = b.bloodPressureDate ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + lastRecord = allResults.first; + final systolic = lastRecord.systolicePressure ?? 0; + final diastolic = lastRecord.diastolicPressure ?? 0; + displayValue = '$systolic/$diastolic'; + unit = 'mmHg'; + lastDate = lastRecord.bloodPressureDate; + final (s, c, _) = _getBloodPressureStatus(systolic); + status = s; + statusColor = c; + } + break; + + case HealthTrackerTypeEnum.weightTracker: + final allResults = [ + ...viewModel.weekWeightMeasurementResult, + ...viewModel.monthWeightMeasurementResult, + ...viewModel.yearWeightMeasurementResult, + ]; + if (allResults.isNotEmpty) { + allResults.sort((a, b) { + final dateA = a.weightDate ?? DateTime(1900); + final dateB = b.weightDate ?? DateTime(1900); + return dateB.compareTo(dateA); + }); + lastRecord = allResults.first; + final weightValue = lastRecord.weightMeasured?.toDouble() ?? 0.0; + displayValue = weightValue.toStringAsFixed(0); + unit = lastRecord.unit ?? 'kg'; + lastDate = lastRecord.weightDate; + final (s, c, _) = _getWeightStatus(); + status = s; + statusColor = c; + } + break; + } + + final formattedDate = lastDate != null ? DateFormat('EEE DD MMM, yy').format(lastDate) : DateFormat('EEE DD MMM, yy').format(DateTime.now()); + + // Show shimmer while loading + if (viewModel.isLoading) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 24.w), + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Shimmer.fromColors( + baseColor: AppColors.shimmerBaseColor, + highlightColor: AppColors.shimmerHighlightColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 40.h, + width: 120.w, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + ), + SizedBox(height: 8.h), + Row( + children: [ + Container( + height: 32.h, + width: 150.w, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(16.r), + ), + ), + SizedBox(width: 8.w), + Container( + height: 32.h, + width: 80.w, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(16.r), + ), + ), + ], + ), + ], + ), + ), + ); + } + + // Show empty state if no records + if (lastRecord == null) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 24.w), + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "--".toText32(isBold: true, color: AppColors.greyTextColor), + SizedBox(width: 6.w), + unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h), + ], + ), + SizedBox(height: 8.h), + AppCustomChipWidget( + labelText: "No records yet".needTranslation, + icon: AppAssets.doctor_calendar_icon, + ), + ], + ), + ); + } + + return Container( + margin: EdgeInsets.symmetric(horizontal: 24.w), + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + displayValue.toText32(isBold: true, color: statusColor), + SizedBox(width: 6.w), + unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + AppCustomChipWidget( + labelText: "${"Last Record".needTranslation}: $formattedDate", + icon: AppAssets.doctor_calendar_icon, + ), + SizedBox(width: 8.w), + if (trackerType != HealthTrackerTypeEnum.weightTracker) ...[ + AppCustomChipWidget( + labelText: status.needTranslation, + icon: AppAssets.normalStatusGreenIcon, + iconColor: statusColor, + ), + ] + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 3b31d38..a5903db 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -45,12 +45,13 @@ class ServicesPage extends StatelessWidget { late final List hmgServices = [ HmgServicesComponentModel( - 11, - "Emergency Services".needTranslation, - "".needTranslation, - AppAssets.emergency_services_icon, - bgColor: AppColors.primaryRedColor, - true, route: null, onTap: () { + 11, + "Emergency Services".needTranslation, + "".needTranslation, + AppAssets.emergency_services_icon, + bgColor: AppColors.primaryRedColor, + true, + route: null, onTap: () { getIt.get().flushData(); getIt.get().getTransportationOrders( showLoader: false, @@ -162,6 +163,15 @@ class ServicesPage extends StatelessWidget { ]; late final List hmgHealthToolServices = [ + HmgServicesComponentModel( + 11, + "Health Trackers".needTranslation, + "".needTranslation, + AppAssets.general_health, + bgColor: AppColors.whiteColor, + true, + route: AppRoutes.healthTrackersPage, + ), HmgServicesComponentModel( 11, "Daily Water Monitor".needTranslation, @@ -179,14 +189,14 @@ class ServicesPage extends StatelessWidget { LoaderBottomSheet.hideLoader(); if (userDetail == null) { waterMonitorVM.populateFromAuthenticatedUser(); - context.navigateWithName(AppRoutes.waterMonitorSettingsScreen); + context.navigateWithName(AppRoutes.waterMonitorSettingsPage); } else { - context.navigateWithName(AppRoutes.waterConsumptionScreen); + context.navigateWithName(AppRoutes.waterConsumptionPage); } }, onError: (error) { LoaderBottomSheet.hideLoader(); - context.navigateWithName(AppRoutes.waterConsumptionScreen); + context.navigateWithName(AppRoutes.waterConsumptionPage); }, ); }, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 95884d4..138865d 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -537,7 +537,7 @@ class _LandingPageState extends State { width: double.infinity, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( - padding: EdgeInsets.all(12.h), + padding: EdgeInsets.all(16.h), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 4e0ec10..1786dec 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -52,7 +52,7 @@ class _OrganSelectorPageState extends State { password: password, onSuccess: () { LoaderBottomSheet.hideLoader(); - context.navigateWithName(AppRoutes.symptomsSelectorScreen); + context.navigateWithName(AppRoutes.symptomsSelectorPage); }, onError: (String error) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 188d07b..a63d1e2 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -19,8 +19,8 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; -class PossibleConditionsScreen extends StatelessWidget { - const PossibleConditionsScreen({super.key}); +class PossibleConditionsPage extends StatelessWidget { + const PossibleConditionsPage({super.key}); Widget _buildLoadingShimmer() { return ListView.separated( diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index aef7ce7..8669c3c 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -41,7 +41,7 @@ class _RiskFactorsScreenState extends State { void _onNextPressed(SymptomsCheckerViewModel viewModel) { if (viewModel.hasSelectedRiskFactors) { - context.navigateWithName(AppRoutes.suggestionsScreen); + context.navigateWithName(AppRoutes.suggestionsPage); } else { dialogService.showErrorBottomSheet( message: 'Please select at least one risk before proceeding'.needTranslation, diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index b5b4388..d0d5b2f 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -43,7 +43,7 @@ class _SuggestionsScreenState extends State { void _onNextPressed(SymptomsCheckerViewModel viewModel) { if (viewModel.hasSelectedSuggestions) { // Navigate to triage screen - context.navigateWithName(AppRoutes.triageScreen); + context.navigateWithName(AppRoutes.triagePage); } else { dialogService.showErrorBottomSheet( message: 'Please select at least one option before proceeding'.needTranslation, diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index 3950974..d6036c6 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -17,14 +17,14 @@ import 'package:hmg_patient_app_new/widgets/chip/custom_selectable_chip.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; -class SymptomsSelectorScreen extends StatefulWidget { - const SymptomsSelectorScreen({super.key}); +class SymptomsSelectorPage extends StatefulWidget { + const SymptomsSelectorPage({super.key}); @override - State createState() => _SymptomsSelectorScreenState(); + State createState() => _SymptomsSelectorPageState(); } -class _SymptomsSelectorScreenState extends State { +class _SymptomsSelectorPageState extends State { late DialogService dialogService; @override @@ -41,7 +41,7 @@ class _SymptomsSelectorScreenState extends State { void _onNextPressed(SymptomsCheckerViewModel viewModel) { if (viewModel.hasSelectedSymptoms) { // Navigate to triage screen - context.navigateWithName(AppRoutes.riskFactorsScreen); + context.navigateWithName(AppRoutes.riskFactorsPage); } else { dialogService.showErrorBottomSheet( message: 'Please select at least one symptom before proceeding'.needTranslation, diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index ba159bd..9d5d884 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -21,14 +21,14 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; -class TriageScreen extends StatefulWidget { - const TriageScreen({super.key}); +class TriagePage extends StatefulWidget { + const TriagePage({super.key}); @override - State createState() => _TriageScreenState(); + State createState() => _TriagePageState(); } -class _TriageScreenState extends State { +class _TriagePageState extends State { late SymptomsCheckerViewModel viewModel; late DialogService dialogService; @@ -78,7 +78,7 @@ class _TriageScreenState extends State { // Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) { // Navigate to results/possible conditions screen - context.navigateWithName(AppRoutes.possibleConditionsScreen); + context.navigateWithName(AppRoutes.possibleConditionsPage); return; } diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index c3ce68b..b438420 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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'; @@ -90,8 +91,8 @@ class _UserInfoSelectionScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText16(weight: FontWeight.w500), - subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + title.toText14(weight: FontWeight.w500), + subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -106,7 +107,7 @@ class _UserInfoSelectionScreenState extends State { Widget _getDivider() { return Divider( color: AppColors.dividerColor, - ).paddingSymmetrical(0, 16.h); + ).paddingSymmetrical(0, 8.h); } @override @@ -114,8 +115,25 @@ class _UserInfoSelectionScreenState extends State { AppState appState = getIt.get(); String name = ""; + int? userAgeFromDOB; if (appState.isAuthenticated) { - name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} "; + final user = appState.getAuthenticatedUser(); + name = "${user!.firstName!} ${user.lastName!} "; + + // Calculate age from authenticated user's DOB if available + if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) { + try { + DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!); + final now = DateTime.now(); + int age = now.year - dob.year; + if (now.month < dob.month || (now.month == dob.month && now.day < dob.day)) { + age--; + } + userAgeFromDOB = age; + } catch (e) { + // If date parsing fails, ignore + } + } } else { name = "Guest"; } @@ -132,8 +150,9 @@ class _UserInfoSelectionScreenState extends State { // Get display values String genderText = viewModel.selectedGender ?? "Not set"; - // Show age calculated from DOB, not the DOB itself - String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set"; + // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) + int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; + String ageText = displayAge != null ? "$displayAge Years" : "Not set"; String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set"; String weightText = @@ -154,11 +173,11 @@ class _UserInfoSelectionScreenState extends State { padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), child: Column( children: [ - "Hello $name, Is your information up to date?".needTranslation.toText18( + "Hello $name, Is your information up to date?".needTranslation.toText16( weight: FontWeight.w600, color: AppColors.textColor, ), - SizedBox(height: 24.h), + SizedBox(height: 32.h), _buildEditInfoTile( context: context, leadingIcon: AppAssets.genderIcon, @@ -240,13 +259,10 @@ class _UserInfoSelectionScreenState extends State { icon: AppAssets.edit_icon, iconColor: AppColors.primaryRedColor, onPressed: () { - context - .read() - .setUserInfoPage(0, isSinglePageEdit: false); + context.read().setUserInfoPage(0, isSinglePageEdit: false); context.navigateWithName(AppRoutes.userInfoFlowManager); }, - backgroundColor: - AppColors.primaryRedColor.withValues(alpha: 0.11), + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 16.f, @@ -257,22 +273,13 @@ class _UserInfoSelectionScreenState extends State { child: CustomButton( text: "Yes, It is".needTranslation, icon: AppAssets.tickIcon, - iconColor: hasEmptyFields - ? AppColors.greyTextColor - : AppColors.whiteColor, + iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, onPressed: hasEmptyFields ? () {} // Empty function for disabled state - : () => context - .navigateWithName(AppRoutes.organSelectorPage), - backgroundColor: hasEmptyFields - ? AppColors.greyLightColor - : AppColors.primaryRedColor, - borderColor: hasEmptyFields - ? AppColors.greyLightColor - : AppColors.primaryRedColor, - textColor: hasEmptyFields - ? AppColors.greyTextColor - : AppColors.whiteColor, + : () => context.navigateWithName(AppRoutes.organSelectorPage), + backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor, + borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor, + textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, fontSize: 16.f, ), ), diff --git a/lib/presentation/water_monitor/water_consumption_screen.dart b/lib/presentation/water_monitor/water_consumption_page.dart similarity index 95% rename from lib/presentation/water_monitor/water_consumption_screen.dart rename to lib/presentation/water_monitor/water_consumption_page.dart index 5d08bcd..2bd429c 100644 --- a/lib/presentation/water_monitor/water_consumption_screen.dart +++ b/lib/presentation/water_monitor/water_consumption_page.dart @@ -21,14 +21,14 @@ import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; -class WaterConsumptionScreen extends StatefulWidget { - const WaterConsumptionScreen({super.key}); +class WaterConsumptionPage extends StatefulWidget { + const WaterConsumptionPage({super.key}); @override - State createState() => _WaterConsumptionScreenState(); + State createState() => _WaterConsumptionPageState(); } -class _WaterConsumptionScreenState extends State { +class _WaterConsumptionPageState extends State { @override void initState() { super.initState(); @@ -98,37 +98,14 @@ class _WaterConsumptionScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "History".needTranslation.toText16(isBold: true), Row( children: [ - InkWell( - onTap: () => viewModel.setGraphView(!viewModel.isGraphView), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: ScaleTransition( - scale: animation, - child: child, - ), - ); - }, - child: Container( - key: ValueKey(viewModel.isGraphView), - child: Utils.buildSvgWithAssets( - icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon, - height: 24.h, - width: 24.h, - ), - ), - ), - ), + "History".needTranslation.toText16(isBold: true), SizedBox(width: 8.w), InkWell( onTap: () => _showHistoryDurationBottomsheet(context, viewModel), child: Container( - padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 6.h), + padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 6.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( backgroundColor: AppColors.greyColor, borderRadius: 8.r, @@ -137,13 +114,36 @@ class _WaterConsumptionScreenState extends State { child: Row( children: [ viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h), ], ), ), ) ], ), + InkWell( + onTap: () => viewModel.setGraphView(!viewModel.isGraphView), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: ScaleTransition( + scale: animation, + child: child, + ), + ); + }, + child: Container( + key: ValueKey(viewModel.isGraphView), + child: Utils.buildSvgWithAssets( + icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon, + height: 24.h, + width: 24.h, + ), + ), + ), + ), ], ), if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else ...[SizedBox(height: 16.h), _buildHistoryGraph()] diff --git a/lib/presentation/water_monitor/water_monitor_settings_screen.dart b/lib/presentation/water_monitor/water_monitor_settings_page.dart similarity index 97% rename from lib/presentation/water_monitor/water_monitor_settings_screen.dart rename to lib/presentation/water_monitor/water_monitor_settings_page.dart index 3470344..302940c 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_screen.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_page.dart @@ -13,14 +13,14 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; -class WaterMonitorSettingsScreen extends StatefulWidget { - const WaterMonitorSettingsScreen({super.key}); +class WaterMonitorSettingsPage extends StatefulWidget { + const WaterMonitorSettingsPage({super.key}); @override - State createState() => _WaterMonitorSettingsScreenState(); + State createState() => _WaterMonitorSettingsPageState(); } -class _WaterMonitorSettingsScreenState extends State { +class _WaterMonitorSettingsPageState extends State { late DialogService dialogService; @override @@ -68,7 +68,6 @@ class _WaterMonitorSettingsScreenState extends State required Function(String) onSelected, bool useUpperCase = false, }) { - dialogService.showFamilyBottomSheetWithoutHWithChild( label: title.needTranslation, message: "", diff --git a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart index 9779562..2359904 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -94,7 +94,7 @@ class WaterActionButtonsWidget extends StatelessWidget { ), _buildActionButton( context: context, - onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsScreen), + onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsPage), title: "Settings".needTranslation, icon: Icon( Icons.settings, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 78045b5..c57ffb9 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -6,8 +6,12 @@ import 'package:hmg_patient_app_new/presentation/authentication/register_step2.d import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; +import 'package:hmg_patient_app_new/presentation/covid19test/covid19_landing_page.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; @@ -23,12 +27,10 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_sele import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart'; import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart'; -import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_screen.dart'; -import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_screen.dart'; +import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart'; +import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; -import '../presentation/covid19test/covid19_landing_page.dart'; - class AppRoutes { static const String initialRoute = '/initialRoute'; static const String loginScreen = '/loginScreen'; @@ -47,25 +49,29 @@ class AppRoutes { static const String huaweiHealthExample = '/huaweiHealthExample'; static const String covid19Test = '/covid19Test'; static const String vitalSign = '/vitalSign'; + //appointments static const String bookAppointmentPage = '/bookAppointmentPage'; // Water Monitor - static const String waterConsumptionScreen = '/waterConsumptionScreen'; - static const String waterMonitorSettingsScreen = '/waterMonitorSettingsScreen'; + static const String waterConsumptionPage = '/waterConsumptionScreen'; + static const String waterMonitorSettingsPage = '/waterMonitorSettingsScreen'; // Symptoms Checker static const String organSelectorPage = '/organSelectorPage'; - static const String symptomsSelectorScreen = '/symptomsCheckerScreen'; - static const String suggestionsScreen = '/suggestionsScreen'; - static const String riskFactorsScreen = '/riskFactorsScreen'; - static const String possibleConditionsScreen = '/possibleConditionsScreen'; - static const String triageScreen = '/triageProgressScreen'; - - //UserInfoSelection + static const String symptomsSelectorPage = '/symptomsCheckerScreen'; + static const String suggestionsPage = '/suggestionsScreen'; + static const String riskFactorsPage = '/riskFactorsScreen'; + static const String possibleConditionsPage = '/possibleConditionsScreen'; + static const String triagePage = '/triageProgressScreen'; static const String userInfoSelection = '/userInfoSelection'; static const String userInfoFlowManager = '/userInfoFlowManager'; + // Health Trackers + static const String healthTrackersPage = '/healthTrackersListScreen'; + static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage'; + static const String healthTrackerDetailPage = '/healthTrackerDetailPage'; + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -78,11 +84,11 @@ class AppRoutes { comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), homeHealthCarePage: (context) => HhcProceduresPage(), organSelectorPage: (context) => OrganSelectorPage(), - symptomsSelectorScreen: (context) => SymptomsSelectorScreen(), - riskFactorsScreen: (context) => RiskFactorsScreen(), - suggestionsScreen: (context) => SuggestionsScreen(), - possibleConditionsScreen: (context) => PossibleConditionsScreen(), - triageScreen: (context) => TriageScreen(), + symptomsSelectorPage: (context) => SymptomsSelectorPage(), + riskFactorsPage: (context) => RiskFactorsScreen(), + suggestionsPage: (context) => SuggestionsScreen(), + possibleConditionsPage: (context) => PossibleConditionsPage(), + triagePage: (context) => TriagePage(), bloodDonationPage: (context) => BloodDonationPage(), bookAppointmentPage: (context) => BookAppointmentPage(), userInfoSelection: (context) => UserInfoSelectionScreen(), @@ -90,11 +96,23 @@ class AppRoutes { smartWatches: (context) => SmartwatchInstructionsPage(), huaweiHealthExample: (context) => HuaweiHealthExample(), covid19Test: (context) => Covid19LandingPage(), - // - waterConsumptionScreen: (context) => WaterConsumptionScreen(), - waterMonitorSettingsScreen: (context) => WaterMonitorSettingsScreen(), + waterConsumptionPage: (context) => WaterConsumptionPage(), + waterMonitorSettingsPage: (context) => WaterMonitorSettingsPage(), healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator), healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter), - vitalSign: (context) => VitalSignPage() + healthTrackersPage: (context) => HealthTrackersPage(), + vitalSign: (context) => VitalSignPage(), + addHealthTrackerEntryPage: (context) { + final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; + return AddHealthTrackerEntryPage( + trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, + ); + }, + healthTrackerDetailPage: (context) { + final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; + return HealthTrackerDetailPage( + trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, + ); + }, }; } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index c631f5b..aee425c 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -2,111 +2,112 @@ import 'package:flutter/material.dart'; class AppColors { static const transparent = Colors.transparent; - static const mainPurple = Color(0xFF7954F7); + static const mainPurple = Color(0xFF7954F7); // #7954F7 - static const scaffoldBgColor = Color(0xFFF8F8F8); - static const bottomSheetBgColor = Color(0xFFF8F8FA); - static const lightGreyEFColor = Color(0xffeaeaff); - static const greyF7Color = Color(0xffF7F7F7); - static const greyInfoTextColor = Color(0xff777777); - static const lightGrayColor = Color(0xff808080); - static const greyTextColorLight = Color(0xFFA2A2A2); + static const scaffoldBgColor = Color(0xFFF8F8F8); // #F8F8F8 + static const bottomSheetBgColor = Color(0xFFF8F8FA); // #F8F8FA + static const lightGreyEFColor = Color(0xffeaeaff); // #EAEAFF + static const greyF7Color = Color(0xffF7F7F7); // #F7F7F7 + static const greyInfoTextColor = Color(0xff777777); // #777777 + static const lightGrayColor = Color(0xff808080); // #808080 + static const greyTextColorLight = Color(0xFFA2A2A2); // #A2A2A2 // New UI Colors - static const whiteColor = Color(0xFFffffff); - static const Color bgScaffoldColor = Color(0xffF8F8F8); - static const Color primaryRedColor = Color(0xFFED1C2B); - static const Color primaryRedBorderColor = Color(0xFFED1C2B); - static const Color secondaryLightRedColor = Color(0xFFFEE9EA); - static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA); - static const Color bgRedLightColor = Color(0xFFFEE9EA); - static const Color bgGreenColor = Color(0xFF18C273); - static const Color textColor = Color(0xFF2E3039); - static const Color borderGrayColor = Color(0x332E3039); - static const Color textColorLight = Color(0xFF5E5E5E); - static const Color borderOnlyColor = Color(0xFF2E3039); - static const Color chipBorderColorOpacity20 = Color(0x332E3039); - static const Color dividerColor = Color(0x40D2D2D2); - static const Color warningColorYellow = Color(0xFFF4A308); - static const Color blackBgColor = Color(0xFF2E3039); - static const blackColor = textColor; - static const Color inputLabelTextColor = Color(0xff898A8D); - static const Color greyTextColor = Color(0xFF8F9AA3); - static const Color lightGrayBGColor = Color(0x142E3039); - static const Color checkBoxBorderColor = Color(0xffD2D2D2); - - static const Color pharmacyBGColor = Color(0xFF359846); - - static const lightGreenColor = Color(0xFF0ccedde); - static const textGreenColor = Color(0xFF18C273); - static const Color ratingColorYellow = Color(0xFFFFAF15); - static const Color spacerLineColor = Color(0x2E30391A); + static const whiteColor = Color(0xFFffffff); // #FFFFFF + static const Color bgScaffoldColor = Color(0xffF8F8F8); // #F8F8F8 + static const Color primaryRedColor = Color(0xFFED1C2B); // #ED1C2B + static const Color primaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B + static const Color secondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA + static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA); // #FEE9EA + static const Color bgRedLightColor = Color(0xFFFEE9EA); // #FEE9EA + static const Color bgGreenColor = Color(0xFF18C273); // #18C273 + static const Color textColor = Color(0xFF2E3039); // #2E3039 + static const Color borderGrayColor = Color(0x332E3039); // #2E3039 (20% opacity) + static const Color textColorLight = Color(0xFF5E5E5E); // #5E5E5E + static const Color borderOnlyColor = Color(0xFF2E3039); // #2E3039 + static const Color chipBorderColorOpacity20 = Color(0x332E3039); // #2E3039 (20% opacity) + static const Color dividerColor = Color(0x40D2D2D2); // #D2D2D2 (25% opacity) + static const Color warningColorYellow = Color(0xFFF4A308); // #F4A308 + static const Color blackBgColor = Color(0xFF2E3039); // #2E3039 + static const blackColor = textColor; // #2E3039 + static const Color inputLabelTextColor = Color(0xff898A8D); // #898A8D + static const Color greyTextColor = Color(0xFF8F9AA3); // #8F9AA3 + static const Color lightGrayBGColor = Color(0x142E3039); // #2E3039 (8% opacity) + static const Color checkBoxBorderColor = Color(0xffD2D2D2); // #D2D2D2 + + static const Color pharmacyBGColor = Color(0xFF359846); // #359846 + + static const lightGreenColor = Color(0xFF0ccedde); // #0CCEDDE + static const textGreenColor = Color(0xFF18C273); // #18C273 + static const Color ratingColorYellow = Color(0xFFFFAF15); // #FFAF15 + static const Color spacerLineColor = Color(0x2E30391A); // #2E3039 (10% opacity) //Chips - static const Color successColor = Color(0xff18C273); - static const Color successLightBgColor = Color(0xffDDF6EA); - static const Color errorColor = Color(0xFFED1C2B); - static const Color alertColor = Color(0xFFD48D05); - static const Color infoColor = Color(0xFF0B85F7); - static const Color warningColor = Color(0xFFFFCC00); - static const Color greyColor = Color(0xFFEFEFF0); - static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); - static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); - - static const Color successLightColor = Color(0xFF18C273); - static const Color errorLightColor = Color(0xFFED1C2B); - static const Color alertLightColor = Color(0xFFD48D05); - static const Color infoLightColor = Color(0xFF0B85F7); - static const Color warningLightColor = Color(0xFFFFCC00); - static const Color greyLightColor = Color(0xFFEFEFF0); - static const Color thumbColor = Color(0xFF18C273); - static const Color switchBackgroundColor = Color(0x2618C273); - - static const Color bottomNAVBorder = Color(0xFFEEEEEE); - - static const Color quickLoginColor = Color(0xFF666666); - - static const Color tooltipTextColor = Color(0xFF414D55); - static const Color graphGridColor = Color(0x4D18C273); - static const Color criticalLowAndHigh = Color(0xFFED1C2B); - static const Color highAndLow = Color(0xFFFFAF15); - static const Color labelTextColor = Color(0xFF838383); - static const Color calenderTextColor = Color(0xFFD0D0D0); - static const Color lightGreenButtonColor = Color(0x2618C273); - - static const Color lightRedButtonColor = Color(0x1AED1C2B); + static const Color successColor = Color(0xff18C273); // #18C273 + static const Color successLightBgColor = Color(0xffDDF6EA); // #DDF6EA + static const Color errorColor = Color(0xFFED1C2B); // #ED1C2B + static const Color alertColor = Color(0xFFD48D05); // #D48D05 + static const Color infoColor = Color(0xFF0B85F7); // #0B85F7 + static const Color warningColor = Color(0xFFFFCC00); // #FFCC00 + static const Color greyColor = Color(0xFFEFEFF0); // #EFEFF0 + static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B + static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA + // static const Color chipSecondaryLightRedColor = Color(0xFFFF9E15); // #FEE9EA + + static const Color successLightColor = Color(0xFF18C273); // #18C273 + static const Color errorLightColor = Color(0xFFED1C2B); // #ED1C2B + static const Color alertLightColor = Color(0xFFD48D05); // #D48D05 + static const Color infoLightColor = Color(0xFF0B85F7); // #0B85F7 + static const Color warningLightColor = Color(0xFFFFCC00); // #FFCC00 + static const Color greyLightColor = Color(0xFFEFEFF0); // #EFEFF0 + static const Color thumbColor = Color(0xFF18C273); // #18C273 + static const Color switchBackgroundColor = Color(0x2618C273); // #18C273 (15% opacity) + + static const Color bottomNAVBorder = Color(0xFFEEEEEE); // #EEEEEE + + static const Color quickLoginColor = Color(0xFF666666); // #666666 + + static const Color tooltipTextColor = Color(0xFF414D55); // #414D55 + static const Color graphGridColor = Color(0x4D18C273); // #18C273 (30% opacity) + static const Color criticalLowAndHigh = Color(0xFFED1C2B); // #ED1C2B + static const Color highAndLow = Color(0xFFFFAF15); // #FFAF15 + static const Color labelTextColor = Color(0xFF838383); // #838383 + static const Color calenderTextColor = Color(0xFFD0D0D0); // #D0D0D0 + static const Color lightGreenButtonColor = Color(0x2618C273); // #18C273 (15% opacity) + + static const Color lightRedButtonColor = Color(0x1AED1C2B); // #ED1C2B (10% opacity) // Status Colors - static const Color statusPendingColor = Color(0xffCC9B14); - static const Color statusProcessingColor = Color(0xff2E303A); - static const Color statusCompletedColor = Color(0xff359846); - static const Color statusRejectedColor = Color(0xffD02127); + static const Color statusPendingColor = Color(0xffCC9B14); // #CC9B14 + static const Color statusProcessingColor = Color(0xff2E303A); // #2E303A + static const Color statusCompletedColor = Color(0xff359846); // #359846 + static const Color statusRejectedColor = Color(0xffD02127); // #D02127 // Info Banner Colors - static const Color infoBannerBgColor = Color(0xFFFFF4E6); - static const Color infoBannerBorderColor = Color(0xFFFFE5B4); - static const Color infoBannerIconColor = Color(0xFFCC9B14); - static const Color infoBannerTextColor = Color(0xFF856404); + static const Color infoBannerBgColor = Color(0xFFFFF4E6); // #FFF4E6 + static const Color infoBannerBorderColor = Color(0xFFFFE5B4); // #FFE5B4 + static const Color infoBannerIconColor = Color(0xFFCC9B14); // #CC9B14 + static const Color infoBannerTextColor = Color(0xFF856404); // #856404 // SymptomsChecker - static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); - static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); - static const Color chipColorMonitor = Color(0xFF18C273); - static const Color chipColorEmergency = Color(0xFFED1C2B); + static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); // #FFAF15 + static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); // #AB7103 + static const Color chipColorMonitor = Color(0xFF18C273); // #18C273 + static const Color chipColorEmergency = Color(0xFFED1C2B); // #ED1C2B // Services Page Colors - static const Color eReferralCardColor = Color(0xFFFF8012); - static const Color bloodDonationCardColor = Color(0xFFFF5662); - static const Color bookAppointment = Color(0xFF415364); + static const Color eReferralCardColor = Color(0xFFFF8012); // #FF8012 + static const Color bloodDonationCardColor = Color(0xFFFF5662); // #FF5662 + static const Color bookAppointment = Color(0xFF415364); // #415364 // Water Monitor - static const Color blueColor = Color(0xFF4EB5FF); - static const Color blueGradientColorOne = Color(0xFFF1F7FD); - static const Color blueGradientColorTwo = Color(0xFFD9EFFF); + static const Color blueColor = Color(0xFF4EB5FF); // #4EB5FF + static const Color blueGradientColorOne = Color(0xFFF1F7FD); // #F1F7FD + static const Color blueGradientColorTwo = Color(0xFFD9EFFF); // #D9EFFF // Shimmer - static const Color shimmerBaseColor = Color(0xFFE0E0E0); - static const Color shimmerHighlightColor = Color(0xFFF5F5F5); - static const Color covid29Color = Color(0xff2563EB); + static const Color shimmerBaseColor = Color(0xFFE0E0E0); // #E0E0E0 + static const Color shimmerHighlightColor = Color(0xFFF5F5F5); // #F5F5F5 + static const Color covid29Color = Color(0xff2563EB); // #2563EB } diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index 1f5d806..ad47cd2 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; /// /// **Parameters:** /// - [dataPoints]: List of `DataPoint` objects to plot. +/// - [secondaryDataPoints]: Optional list for a second line (e.g., diastolic in blood pressure). /// - [leftLabelFormatter]: Function to build left axis labels. /// - [bottomLabelFormatter]: Function to build bottom axis labels. /// - [width]: Optional width of the chart. @@ -18,6 +19,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; /// - [maxY], [maxX], [minX]: Axis bounds. /// - [spotColor]: Color of the touched spot marker. /// - [graphColor]: Color of the line. +/// - [secondaryGraphColor]: Color of the secondary line. /// - [graphShadowColor]: Color of the area below the line. /// - [graphGridColor]: Color of the grid lines. /// - [bottomLabelColor]: Color of bottom axis labels. @@ -43,6 +45,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; /// ) class CustomGraph extends StatelessWidget { final List dataPoints; + final List? secondaryDataPoints; // For dual-line graphs (e.g., blood pressure) final double? width; final double height; final double? maxY; @@ -50,6 +53,7 @@ class CustomGraph extends StatelessWidget { final double? minX; final Color spotColor; final Color graphColor; + final Color? secondaryGraphColor; // Color for secondary line final Color graphShadowColor; final Color graphGridColor; final Color bottomLabelColor; @@ -79,6 +83,7 @@ class CustomGraph extends StatelessWidget { const CustomGraph( {super.key, required this.dataPoints, + this.secondaryDataPoints, required this.leftLabelFormatter, this.width, required this.scrollDirection, @@ -89,6 +94,7 @@ class CustomGraph extends StatelessWidget { this.isFullScreeGraph = false, this.spotColor = AppColors.bgGreenColor, this.graphColor = AppColors.bgGreenColor, + this.secondaryGraphColor, this.graphShadowColor = AppColors.graphGridColor, this.graphGridColor = AppColors.graphGridColor, this.bottomLabelColor = AppColors.textColor, @@ -225,7 +231,7 @@ class CustomGraph extends StatelessWidget { return FlSpot(entry.key.toDouble(), value); }).toList(); - var data = [ + var data = [ LineChartBarData( spots: allSpots, isCurved: true, @@ -254,6 +260,31 @@ class CustomGraph extends StatelessWidget { ) ]; + // Add secondary line if provided (for dual-line graphs like blood pressure) + if (secondaryDataPoints != null && secondaryDataPoints!.isNotEmpty) { + final List secondarySpots = secondaryDataPoints!.asMap().entries.map((entry) { + double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value; + return FlSpot(entry.key.toDouble(), value); + }).toList(); + + data.add( + LineChartBarData( + spots: secondarySpots, + isCurved: true, + isStrokeCapRound: true, + isStrokeJoinRound: true, + barWidth: 2, + gradient: LinearGradient( + colors: [secondaryGraphColor ?? AppColors.blueColor, secondaryGraphColor ?? AppColors.blueColor], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + dotData: FlDotData(show: showLinePoints), + belowBarData: BarAreaData(show: false), + ), + ); + } + return data; } } diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 94ed9f8..9f101d4 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -3,17 +3,14 @@ import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; - -import '../core/dependencies.dart'; - -// TODO: Import AppColors if bgRedColor is defined there -// import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart'; +import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart'; class TextInputWidget extends StatelessWidget { final String labelText; @@ -49,6 +46,7 @@ class TextInputWidget extends StatelessWidget { final int maxLines; final Color? hintColor; final bool? isHideSwitcher; + final bool? isArrowTrailing; // final List countryList; // final Function(Country)? onCountryChange; @@ -87,6 +85,7 @@ class TextInputWidget extends StatelessWidget { this.minLines = 3, this.maxLines = 6, this.isHideSwitcher, + this.isArrowTrailing, // this.countryList = const [], // this.onCountryChange, }); @@ -167,7 +166,8 @@ class TextInputWidget extends StatelessWidget { ], ), ), - if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), + if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context, isArrowTrailing: isArrowTrailing ?? false), + if (selectionType == SelectionTypeEnum.time) _buildTimePickerIcon(context, isArrowTrailing: isArrowTrailing ?? false), if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context), ], ), @@ -200,7 +200,7 @@ class TextInputWidget extends StatelessWidget { child: Utils.buildSvgWithAssets(icon: leadingIcon!)); } - Widget _buildTrailingIcon(BuildContext context) { + Widget _buildTrailingIcon(BuildContext context, {bool isArrowTrailing = false}) { final AppState appState = getIt.get(); return Container( height: 40.h, @@ -233,7 +233,47 @@ class TextInputWidget extends StatelessWidget { onChange!(picked.toString()); } }, - child: Utils.buildSvgWithAssets(icon: AppAssets.calendar), + child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.calendar), + ), + ); + } + + Widget _buildTimePickerIcon(BuildContext context, {bool isArrowTrailing = false}) { + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.zero, + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + borderRadius: 12.r, + color: AppColors.whiteColor, + ), + child: GestureDetector( + onTap: () async { + // Parse existing time if available + TimeOfDay? initialTime; + if (controller?.text.isNotEmpty ?? false) { + initialTime = TimePickerWidget.parseTime(controller!.text); + } + + final picked = await TimePickerWidget.show( + context, + initialTime: initialTime, + use24HourFormat: false, // You can make this configurable if needed + onTimeSelected: (time) { + if (onChange != null) { + final formattedTime = TimePickerWidget.formatTime(time); + onChange!(formattedTime); + } + }, + ); + + // Update controller if time was picked + if (picked != null && controller != null) { + controller!.text = TimePickerWidget.formatTime(picked); + } + }, + child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.alarm_clock_icon), ), ); } diff --git a/lib/widgets/time_picker_widget.dart b/lib/widgets/time_picker_widget.dart new file mode 100644 index 0000000..71d9ab2 --- /dev/null +++ b/lib/widgets/time_picker_widget.dart @@ -0,0 +1,348 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +/// A reusable time picker widget that can be used anywhere in the app +/// Shows a bottom sheet with iOS-style time picker +class TimePickerWidget { + /// Shows a time picker bottom sheet + /// + /// [context] - BuildContext for showing the bottom sheet + /// [initialTime] - Initial time to display (defaults to current time) + /// [use24HourFormat] - Whether to use 24-hour format (defaults to false) + /// [onTimeSelected] - Callback when time is selected + /// + /// Returns the selected TimeOfDay or null if cancelled + static Future show( + BuildContext context, { + TimeOfDay? initialTime, + bool use24HourFormat = false, + bool displaySelectedTime = false, + Function(TimeOfDay)? onTimeSelected, + }) async { + final selectedTime = initialTime ?? TimeOfDay.now(); + + final result = await showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (BuildContext context) { + return _TimePickerBottomSheet( + initialTime: selectedTime, + use24HourFormat: use24HourFormat, + displaySelectedTime: displaySelectedTime, + onTimeChanged: (time) { + // Time is being changed in real-time + }, + ); + }, + ); + + if (result != null && onTimeSelected != null) { + onTimeSelected(result); + } + + return result; + } + + /// Formats TimeOfDay to string (HH:mm format) + static String formatTime(TimeOfDay time, {bool use24HourFormat = false}) { + if (use24HourFormat) { + return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } else { + final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod; + final period = time.period == DayPeriod.am ? 'AM' : 'PM'; + return '${hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')} $period'; + } + } + + /// Parses time string to TimeOfDay + static TimeOfDay? parseTime(String timeString) { + try { + final parts = timeString.split(':'); + if (parts.length == 2) { + final hour = int.parse(parts[0]); + final minute = int.parse(parts[1].split(' ')[0]); + return TimeOfDay(hour: hour, minute: minute); + } + } catch (e) { + return null; + } + return null; + } +} + +class _TimePickerBottomSheet extends StatefulWidget { + final TimeOfDay initialTime; + final bool use24HourFormat; + final bool displaySelectedTime; + final Function(TimeOfDay) onTimeChanged; + + const _TimePickerBottomSheet({ + required this.initialTime, + required this.use24HourFormat, + required this.displaySelectedTime, + required this.onTimeChanged, + }); + + @override + State<_TimePickerBottomSheet> createState() => _TimePickerBottomSheetState(); +} + +class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { + late int selectedHour; + late int selectedMinute; + late DayPeriod selectedPeriod; + + @override + void initState() { + super.initState(); + selectedHour = widget.use24HourFormat ? widget.initialTime.hour : widget.initialTime.hourOfPeriod; + if (selectedHour == 0 && !widget.use24HourFormat) selectedHour = 12; + selectedMinute = widget.initialTime.minute; + selectedPeriod = widget.initialTime.period; + } + + TimeOfDay _getCurrentTime() { + if (widget.use24HourFormat) { + return TimeOfDay(hour: selectedHour, minute: selectedMinute); + } else { + int hour = selectedHour; + if (selectedPeriod == DayPeriod.pm && hour != 12) { + hour += 12; + } else if (selectedPeriod == DayPeriod.am && hour == 12) { + hour = 0; + } + return TimeOfDay(hour: hour, minute: selectedMinute); + } + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.r), + topRight: Radius.circular(20.r), + ), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.dividerColor, + width: 1, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Select Time".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Utils.buildSvgWithAssets( + icon: AppAssets.cancel, + width: 24.h, + height: 24.h, + iconColor: AppColors.textColor, + ), + ), + ], + ), + ), + + // Time Picker + SizedBox( + height: 250.h, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Hour Picker + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: widget.use24HourFormat ? selectedHour : (selectedHour - 1), + ), + itemExtent: 50.h, + onSelectedItemChanged: (index) { + setState(() { + if (widget.use24HourFormat) { + selectedHour = index; + } else { + selectedHour = index + 1; + } + widget.onTimeChanged(_getCurrentTime()); + }); + }, + children: List.generate( + widget.use24HourFormat ? 24 : 12, + (index) { + final hour = widget.use24HourFormat ? index : index + 1; + return Center( + child: Text( + hour.toString().padLeft(2, '0'), + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + ); + }, + ), + ), + ), + + // Separator + Text( + ':', + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + + // Minute Picker + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: selectedMinute, + ), + itemExtent: 50.h, + onSelectedItemChanged: (index) { + setState(() { + selectedMinute = index; + widget.onTimeChanged(_getCurrentTime()); + }); + }, + children: List.generate( + 60, + (index) => Center( + child: Text( + index.toString().padLeft(2, '0'), + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + ), + ), + ), + ), + + // AM/PM Picker (only for 12-hour format) + if (!widget.use24HourFormat) + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: selectedPeriod == DayPeriod.am ? 0 : 1, + ), + itemExtent: 50.h, + onSelectedItemChanged: (index) { + setState(() { + selectedPeriod = index == 0 ? DayPeriod.am : DayPeriod.pm; + widget.onTimeChanged(_getCurrentTime()); + }); + }, + children: [ + Center( + child: Text( + 'AM', + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + ), + Center( + child: Text( + 'PM', + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + ), + ], + ), + ), + ], + ), + ), + + if (widget.displaySelectedTime) + // Current Time Display + Container( + margin: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), + padding: EdgeInsets.symmetric(vertical: 12.h), + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: TimePickerWidget.formatTime( + _getCurrentTime(), + use24HourFormat: widget.use24HourFormat, + ).toText20( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + ), + ), + + // Action Buttons + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), + child: Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: "Cancel".needTranslation, + onPressed: () => Navigator.pop(context), + textColor: AppColors.textColor, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + height: 56.h, + text: "Confirm".needTranslation, + onPressed: () { + Navigator.pop(context, _getCurrentTime()); + }, + textColor: AppColors.whiteColor, + backgroundColor: AppColors.primaryRedColor, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/time_picker_widget_usage_example.dart b/lib/widgets/time_picker_widget_usage_example.dart new file mode 100644 index 0000000..9ff6c35 --- /dev/null +++ b/lib/widgets/time_picker_widget_usage_example.dart @@ -0,0 +1,165 @@ +// Example usage of TimePickerWidget +// +// This file demonstrates how to use the TimePickerWidget in your Flutter app. +// The TimePickerWidget is a reusable component that shows a bottom sheet with +// an iOS-style time picker. + +// ============================================================================ +// EXAMPLE 1: Using with TextInputWidget +// ============================================================================ +/* +TextInputWidget( + labelText: "Appointment Time", + hintText: "Select time", + controller: timeController, + selectionType: SelectionTypeEnum.time, + isReadOnly: true, + onChange: (value) { + print("Selected time: $value"); + }, +) +*/ + +// ============================================================================ +// EXAMPLE 2: Direct usage with custom button +// ============================================================================ +/* +ElevatedButton( + onPressed: () async { + final selectedTime = await TimePickerWidget.show( + context, + initialTime: TimeOfDay.now(), + use24HourFormat: false, + onTimeSelected: (time) { + print("Time selected: ${TimePickerWidget.formatTime(time)}"); + }, + ); + + if (selectedTime != null) { + print("Final time: ${TimePickerWidget.formatTime(selectedTime)}"); + } + }, + child: Text("Pick Time"), +) +*/ + +// ============================================================================ +// EXAMPLE 3: Using with 24-hour format +// ============================================================================ +/* +TextInputWidget( + labelText: "Meeting Time", + hintText: "Select time (24h)", + controller: timeController, + selectionType: SelectionTypeEnum.time, + isReadOnly: true, + onChange: (value) { + // The value will be formatted as "14:30" for 2:30 PM in 24h format + print("Selected time (24h): $value"); + }, +) + +// Or programmatically: +final time = await TimePickerWidget.show( + context, + use24HourFormat: true, // Enable 24-hour format +); +*/ + +// ============================================================================ +// EXAMPLE 4: Parsing and formatting times +// ============================================================================ +/* +// Parse time string to TimeOfDay +String timeString = "02:30 PM"; +TimeOfDay? parsedTime = TimePickerWidget.parseTime(timeString); + +// Format TimeOfDay to string +TimeOfDay time = TimeOfDay(hour: 14, minute: 30); +String formatted12h = TimePickerWidget.formatTime(time); // "02:30 PM" +String formatted24h = TimePickerWidget.formatTime(time, use24HourFormat: true); // "14:30" +*/ + +// ============================================================================ +// EXAMPLE 5: Complete form example with date and time +// ============================================================================ +/* +class AppointmentForm extends StatefulWidget { + @override + _AppointmentFormState createState() => _AppointmentFormState(); +} + +class _AppointmentFormState extends State { + final TextEditingController dateController = TextEditingController(); + final TextEditingController timeController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Date picker + TextInputWidget( + labelText: "Appointment Date", + hintText: "Select date", + controller: dateController, + selectionType: SelectionTypeEnum.calendar, + isReadOnly: true, + onChange: (value) { + print("Date selected: $value"); + }, + ), + + SizedBox(height: 16), + + // Time picker + TextInputWidget( + labelText: "Appointment Time", + hintText: "Select time", + controller: timeController, + selectionType: SelectionTypeEnum.time, + isReadOnly: true, + onChange: (value) { + print("Time selected: $value"); + }, + ), + ], + ); + } +} +*/ + +// ============================================================================ +// Features: +// ============================================================================ +// ✅ iOS-style cupertino picker (works on both iOS and Android) +// ✅ Support for 12-hour format (with AM/PM) +// ✅ Support for 24-hour format +// ✅ Beautiful bottom sheet UI matching app design +// ✅ Real-time preview of selected time +// ✅ Confirm/Cancel buttons +// ✅ Easy integration with TextInputWidget +// ✅ Parse and format time utilities +// ✅ Fully customizable and reusable + +// ============================================================================ +// API Reference: +// ============================================================================ +// TimePickerWidget.show() - Shows the time picker bottom sheet +// Parameters: +// - context: BuildContext (required) +// - initialTime: TimeOfDay? (optional, defaults to current time) +// - use24HourFormat: bool (optional, defaults to false) +// - onTimeSelected: Function(TimeOfDay)? (optional callback) +// Returns: Future +// +// TimePickerWidget.formatTime() - Formats TimeOfDay to string +// Parameters: +// - time: TimeOfDay (required) +// - use24HourFormat: bool (optional, defaults to false) +// Returns: String (e.g., "02:30 PM" or "14:30") +// +// TimePickerWidget.parseTime() - Parses time string to TimeOfDay +// Parameters: +// - timeString: String (required, e.g., "02:30 PM") +// Returns: TimeOfDay? + From 83f04bc0e6d5f2402cef9edcae1572407bcae4d7 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 12 Jan 2026 10:19:50 +0300 Subject: [PATCH 22/46] search filter on select doctor page. --- .../book_appointments_view_model.dart | 3 + .../book_appointment/select_doctor_page.dart | 86 +++++++++++-------- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index cbed940..380d2db 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -49,6 +49,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isLiveCareSchedule = false; bool isGetDocForHealthCal = false; + bool showSortFilterButtons = false; int? calculationID = 0; bool isSortByClinic = true; @@ -200,8 +201,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { void filterClinics(String? query) { if (query!.isEmpty) { _filteredClinicsList = List.from(clinicsList); + showSortFilterButtons = false; } else { _filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList(); + showSortFilterButtons = query.length >= 3; } notifyListeners(); } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index d77e152..569e8ac 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -40,9 +40,7 @@ class _SelectDoctorPageState extends State { late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; - // Scroll controller to control page scrolling when a group expands late ScrollController _scrollController; - // Map of keys for each item to allow scrolling to them final Map _itemKeys = {}; @override @@ -79,6 +77,20 @@ class _SelectDoctorPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: "Choose Doctor".needTranslation, + // 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), + // child: CustomButton( + // text: LocaleKeys.search.tr(), + // onPressed: () { + // }, + // icon: null, + // fontSize: 16.f, + // backgroundColor: AppColors.primaryRedColor, + // borderColor: AppColors.primaryRedColor, + // borderRadius: 12.r, + // fontWeight: FontWeight.w500), + // ), child: SingleChildScrollView( controller: _scrollController, child: Padding( @@ -124,40 +136,42 @@ class _SelectDoctorPageState extends State { ], ), SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - bookAppointmentsVM.setIsSortByClinic(true); - }, - backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: bookAppointmentsVM.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: () { - bookAppointmentsVM.setIsSortByClinic(false); - }, - backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, - textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(0.h, 0.h), - SizedBox(height: 16.h), + if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + bookAppointmentsVM.setIsSortByClinic(true); + }, + backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: bookAppointmentsVM.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: () { + bookAppointmentsVM.setIsSortByClinic(false); + }, + backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(0.h, 0.h), + if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) + SizedBox(height: 16.h), Row( mainAxisSize: MainAxisSize.max, children: [ From 1c0842ab9501d70f37d937dd5307c3dc752fd5a5 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 12 Jan 2026 11:10:13 +0300 Subject: [PATCH 23/46] updates --- lib/core/utils/utils.dart | 12 ++--- .../book_appointment/widgets/doctor_card.dart | 20 ++++--- lib/presentation/home/landing_page.dart | 52 +++++++++++++++---- .../notifications_list_page.dart | 12 +++-- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index e8fa650..a4805b3 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -61,13 +61,13 @@ class Utils { "ProjectOutSA": false, "UsingInDoctorApp": false },{ - "Desciption": "Jeddah Hospital", - "DesciptionN": "مستشفى جدة", + "Desciption": "Jeddah Fayhaa Hospital", + "DesciptionN": "مستشفى جدة الفيحاء", "ID": 3, // Campus ID - "LegalName": "Jeddah Hospital", - "LegalNameN": "مستشفى جدة", - "Name": "Jeddah Hospital", - "NameN": "مستشفى جدة", + "LegalName": "Jeddah Fayhaa Hospital", + "LegalNameN": "مستشفى جدة الفيحاء", + "Name": "Jeddah Fayhaa Hospital", + "NameN": "مستشفى جدة الفيحاء", "PhoneNumber": "+966115222222", "SetupID": "013311", "DistanceInKilometers": 0, diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index ffe26ff..da7c47d 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -70,15 +70,19 @@ class DoctorCard extends StatelessWidget { ), SizedBox(height: 2.h), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (isLoading - ? "Consultant Cardiologist" - : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first - : "") - .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) - .toShimmer2(isShow: isLoading), + SizedBox( + width: MediaQuery.of(context).size.width * 0.45, + child: (isLoading + ? "Consultant Cardiologist" + : doctorsListResponseModel.speciality!.isNotEmpty + ? doctorsListResponseModel.speciality!.first + : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 2) + .toShimmer2(isShow: isLoading), + ), SizedBox(width: 6.w), Image.network( isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a0dd27f..9c53adb 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -349,15 +349,49 @@ class _LandingPageState extends State { isFromHomePage: true, ), ).paddingSymmetrical(24.h, 0.h) - : Swiper( - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, - layout: SwiperLayout.STACK, - loop: true, - itemWidth: MediaQuery.of(context).size.width - 48.h, + : isTablet + ? SizedBox( + height: isFoldable ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 3, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox( + width: 10.w, + ), + ), + ) + : Swiper( + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 3, + layout: SwiperLayout.STACK, + loop: true, + itemWidth: MediaQuery.of(context).size.width - 48.h, indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 532b583..99d4270 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -52,10 +53,15 @@ class NotificationsListPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - "Notification Title".toText14(), - SizedBox(height: 8.h), - notificationsVM.notificationsList[index].message!.toText14(), + // "Notification Title".toText14(), + // SizedBox(height: 8.h), + Row( + children: [ + Expanded(child: notificationsVM.notificationsList[index].message!.toText16(isBold: notificationsVM.notificationsList[index].isRead ?? false)), + ], + ), SizedBox(height: 12.h), + DateUtil.formatDateToDate(DateUtil.convertStringToDate(notificationsVM.notificationsList[index].isSentOn!), false).toText14(weight: FontWeight.w500), 1.divider, ], ), From 001808488c9ee29d431f421ca256c53cbb7e6e45 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 12 Jan 2026 11:30:01 +0300 Subject: [PATCH 24/46] Added 'send report to email' feature --- .../health_trackers/health_trackers_repo.dart | 127 ++++- lib/main.dart | 1 + .../health_tracker_detail_page.dart | 183 ++++++- .../health_trackers_view_model.dart | 104 ++++ .../medical_file/medical_file_page.dart | 456 +++++++++--------- 5 files changed, 644 insertions(+), 227 deletions(-) diff --git a/lib/features/health_trackers/health_trackers_repo.dart b/lib/features/health_trackers/health_trackers_repo.dart index e6930f9..2a64fc4 100644 --- a/lib/features/health_trackers/health_trackers_repo.dart +++ b/lib/features/health_trackers/health_trackers_repo.dart @@ -39,6 +39,11 @@ abstract class HealthTrackersRepo { required int lineItemNo, }); + /// Send blood sugar report by email. + Future>> sendBloodSugarReportByEmail({ + required String email, + }); + // ==================== BLOOD PRESSURE ==================== /// Get blood pressure result averages (week, month, year). Future>> getBloodPressureResultAverage(); @@ -68,6 +73,11 @@ abstract class HealthTrackersRepo { required int lineItemNo, }); + /// Send blood pressure report by email. + Future>> sendBloodPressureReportByEmail({ + required String email, + }); + // ==================== WEIGHT MEASUREMENT ==================== /// Get weight measurement result averages (week, month, year). Future>> getWeightMeasurementResultAverage(); @@ -94,6 +104,11 @@ abstract class HealthTrackersRepo { Future>> deactivateWeightMeasurementStatus({ required int lineItemNo, }); + + /// Send weight report by email. + Future>> sendWeightReportByEmail({ + required String email, + }); } class HealthTrackersRepoImp implements HealthTrackersRepo { @@ -322,6 +337,42 @@ class HealthTrackersRepoImp implements HealthTrackersRepo { } } + @override + Future>> sendBloodSugarReportByEmail({ + required String email, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'To': email, + }; + + await apiClient.post( + ApiConsts.sendAverageBloodSugarReport, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + // ==================== BLOOD PRESSURE METHODS ==================== @override @@ -538,6 +589,42 @@ class HealthTrackersRepoImp implements HealthTrackersRepo { } } + @override + Future>> sendBloodPressureReportByEmail({ + required String email, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'To': email, + }; + + await apiClient.post( + ApiConsts.sendAverageBloodPressureReport, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + // ==================== WEIGHT MEASUREMENT METHODS ==================== @override @@ -715,9 +802,7 @@ class HealthTrackersRepoImp implements HealthTrackersRepo { } @override - Future>> deactivateWeightMeasurementStatus({ - required int lineItemNo, - }) async { + Future>> deactivateWeightMeasurementStatus({required int lineItemNo}) async { try { GenericApiModel? apiResponse; Failure? failure; @@ -749,4 +834,40 @@ class HealthTrackersRepoImp implements HealthTrackersRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> sendWeightReportByEmail({ + required String email, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + Map body = { + 'To': email, + }; + + await apiClient.post( + ApiConsts.sendAverageBodyWeightReport, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + }, + ); + + 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/main.dart b/lib/main.dart index f0537b3..5091572 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -219,3 +219,4 @@ class MyApp extends StatelessWidget { } } // flutter pub run easy_localization:generate -S assets/langs -f keys -o locale_keys.g.dart + diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart index a2b82ec..9443f12 100644 --- a/lib/presentation/health_trackers/health_tracker_detail_page.dart +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -2,6 +2,7 @@ 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_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.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/enums.dart'; @@ -9,8 +10,6 @@ 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/health_trackers/models/blood_pressure/week_blood_pressure_result_average.dart'; -import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/year_blood_pressure_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/week_diabetic_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; @@ -22,7 +21,10 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; @@ -1028,7 +1030,182 @@ class _HealthTrackerDetailPageState extends State { } void onSendEmailPressed(BuildContext context) async { - // TODO: Implement send email functionality + _showEmailInputBottomSheet(context); + } + + /// Show email input bottom sheet + void _showEmailInputBottomSheet(BuildContext context) { + final viewModel = context.read(); + final appState = getIt.get(); + final dialogService = getIt.get(); + + // Get user's email from authenticated user + final userEmail = appState.getAuthenticatedUser()?.emailAddress ?? ''; + + // Create email controller and pre-fill if available + final emailController = TextEditingController(text: userEmail); + + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: "Send Report by Email".needTranslation, + message: "", + child: _buildEmailInputContent( + context: context, + emailController: emailController, + viewModel: viewModel, + dialogService: dialogService, + ), + onOkPressed: () {}, + ); + } + + /// Build email input content + Widget _buildEmailInputContent({ + required BuildContext context, + required TextEditingController emailController, + required HealthTrackersViewModel viewModel, + required DialogService dialogService, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Enter your email address to receive the report".needTranslation.toText14( + color: AppColors.textColor, + weight: FontWeight.w400, + ), + SizedBox(height: 16.h), + + // Email Input Field using TextInputWidget + TextInputWidget( + padding: EdgeInsets.symmetric(horizontal: 8.w), + labelText: "Email Address".needTranslation, + hintText: "Enter email address".needTranslation, + controller: emailController, + keyboardType: TextInputType.emailAddress, + isEnable: true, + isBorderAllowed: true, + isAllowRadius: true, + ), + + SizedBox(height: 24.h), + + // Send Button + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: "Send Report".needTranslation, + onPressed: () { + _sendEmailReport( + context: context, + email: emailController.text.trim(), + viewModel: viewModel, + dialogService: dialogService, + ); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ); + } + + /// Send email report based on tracker type + Future _sendEmailReport({ + required BuildContext context, + required String email, + required HealthTrackersViewModel viewModel, + required DialogService dialogService, + }) async { + // Validate email + if (email.isEmpty) { + dialogService.showErrorBottomSheet( + message: "Please enter your email address".needTranslation, + ); + return; + } + + // Basic email validation + final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(email)) { + dialogService.showErrorBottomSheet( + message: "Please enter a valid email address".needTranslation, + ); + return; + } + + // Close the email input bottom sheet + Navigator.of(context).pop(); + + // Call appropriate email function based on tracker type + switch (widget.trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + await viewModel.sendBloodSugarReportByEmail( + email: email, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + + _showSuccessMessage(context, dialogService); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet(message: error); + }, + ); + break; + + case HealthTrackerTypeEnum.bloodPressure: + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + + await viewModel.sendBloodPressureReportByEmail( + email: email, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + + _showSuccessMessage(context, dialogService); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + + dialogService.showErrorBottomSheet(message: error); + }, + ); + break; + + case HealthTrackerTypeEnum.weightTracker: + LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + await viewModel.sendWeightReportByEmail( + email: email, + onSuccess: () { + LoaderBottomSheet.hideLoader(); + + _showSuccessMessage(context, dialogService); + }, + onFailure: (error) { + LoaderBottomSheet.hideLoader(); + + dialogService.showErrorBottomSheet(message: error); + }, + ); + break; + } + } + + /// Show success message + void _showSuccessMessage(BuildContext context, DialogService dialogService) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget( + loadingText: "Report has been sent to your email successfully".needTranslation, + ), + callBackFunc: () {}, + isCloseButtonVisible: false, + isDismissible: true, + isFullScreen: false, + ); } Widget _buildPageShimmer() { diff --git a/lib/presentation/health_trackers/health_trackers_view_model.dart b/lib/presentation/health_trackers/health_trackers_view_model.dart index 059c11e..ce1bdf2 100644 --- a/lib/presentation/health_trackers/health_trackers_view_model.dart +++ b/lib/presentation/health_trackers/health_trackers_view_model.dart @@ -388,6 +388,41 @@ class HealthTrackersViewModel extends ChangeNotifier { } } + /// Send weight report by email + Future sendWeightReportByEmail({ + required String email, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + try { + final result = await healthTrackersRepo.sendWeightReportByEmail( + email: email, + ); + + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + if (onFailure != null) onFailure("Failed to send report by email"); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendWeightReportByEmail: $e'); + + if (onFailure != null) onFailure("An error occurred"); + return false; + } + } + // ==================== BLOOD PRESSURE TRACKING METHODS ==================== /// Fetch blood pressure averages and results @@ -554,6 +589,41 @@ class HealthTrackersViewModel extends ChangeNotifier { } } + /// Send blood pressure report by email + Future sendBloodPressureReportByEmail({ + required String email, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + try { + final result = await healthTrackersRepo.sendBloodPressureReportByEmail( + email: email, + ); + + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + if (onFailure != null) onFailure("Failed to send report by email"); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendBloodPressureReportByEmail: $e'); + + if (onFailure != null) onFailure("An error occurred"); + return false; + } + } + // ==================== BLOOD SUGAR (DIABETIC) TRACKING METHODS ==================== /// Fetch blood sugar averages and results @@ -746,6 +816,40 @@ class HealthTrackersViewModel extends ChangeNotifier { } } + /// Send blood sugar report by email + Future sendBloodSugarReportByEmail({ + required String email, + Function()? onSuccess, + Function(String error)? onFailure, + }) async { + try { + final result = await healthTrackersRepo.sendBloodSugarReportByEmail( + email: email, + ); + + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + if (onFailure != null) onFailure("Failed to send report by email"); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendBloodSugarReportByEmail: $e'); + if (onFailure != null) onFailure("An error occurred"); + return false; + } + } + // Validation method String? _validateBloodSugarEntry(String dateTime) { // Validate blood sugar value diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index f705993..554131a 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -7,10 +7,11 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_config.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/book_appointments/book_appointments_view_model.dart'; @@ -234,9 +235,9 @@ class _MedicalFilePageState extends State { labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), onChipTap: () { navigationService.pushPage( - page: FamilyMedicalScreen( - profiles: medicalFileViewModel.patientFamilyFiles, - onSelect: (FamilyFileResponseModelLists p1) {}, + page: FamilyMedicalScreen( + profiles: medicalFileViewModel.patientFamilyFiles, + onSelect: (FamilyFileResponseModelLists p1) {}, ), ); }, @@ -279,7 +280,8 @@ class _MedicalFilePageState extends State { iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12.w, - backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + backgroundColor: + insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ); }), @@ -381,9 +383,7 @@ class _MedicalFilePageState extends State { width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w, height: 8.h, decoration: BoxDecoration( - color: hmgServicesVM.vitalSignCurrentPage == index - ? AppColors.primaryRedColor - : AppColors.dividerColor, + color: hmgServicesVM.vitalSignCurrentPage == index ? AppColors.primaryRedColor : AppColors.dividerColor, borderRadius: BorderRadius.circular(4.r), ), ), @@ -587,7 +587,8 @@ class _MedicalFilePageState extends State { ? Container( padding: EdgeInsets.all(12.w), width: MediaQuery.of(context).size.width, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), @@ -624,57 +625,58 @@ class _MedicalFilePageState extends State { itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length, itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: MedicalFileAppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - onRescheduleTap: () { - openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); - }, - onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); - await myAppointmentsViewModel.isDoctorAvailable( - projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, - doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, - clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID, - onSuccess: (value) async { - if (value) { - await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + onRescheduleTap: () { + openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); + }, + onAskDoctorTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, + doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, + clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID, + onSuccess: (value) async { + if (value) { + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: + myAppointmentsVM.patientAppointmentsHistoryList[index], + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.askDoctor.tr(context: context), - child: AskDoctorRequestTypeSelect( - askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { + print("Doctor is not available"); + } + }, + onError: (_) { LoaderBottomSheet.hideLoader(); - print("Doctor is not available"); - } - }, - onError: (_) { - LoaderBottomSheet.hideLoader(); - }, - ); - }, + }, + ); + }, + ), ), ), - ), - )); + )); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h), ), @@ -733,116 +735,125 @@ class _MedicalFilePageState extends State { child: Column( children: [ ListView.separated( - itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, + itemCount: + prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, shrinkWrap: true, padding: EdgeInsets.only(left: 0, right: 8.w), physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Row( - children: [ - Image.network( - prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, - width: 40.w, - height: 40.h, - fit: BoxFit.cover, - ).circle(100.r), - SizedBox(width: 16.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), - SizedBox(height: 4.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.w, - runSpacing: 4.w, - children: [ - AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), - false, + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Image.network( + prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, + width: 40.w, + height: 40.h, + fit: BoxFit.cover, + ).circle(100.r), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.w, + runSpacing: 4.w, + children: [ + AppCustomChipWidget( + labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate( + prescriptionVM.patientPrescriptionOrders[index].appointmentDate), + false, + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), - ), - // SizedBox(width: 40.h), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), - ], - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), - ), - ); - }), - ), - )); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - SizedBox(height: 16.h), - const Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: "All Prescriptions".needTranslation, - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.w, - ), + // SizedBox(width: 40.h), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + width: 15.w, + height: 15.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor)), + ], + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage( + isFromAppointments: false, + prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), + ), + ); + }), + ), + )); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionsListPage(), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 12.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.w, ), - SizedBox(width: 6.w), - Expanded( - child: CustomButton( - text: "All Medications".needTranslation, - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.all_medications_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), + ), + SizedBox(width: 6.w), + Expanded( + child: CustomButton( + text: "All Medications".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 12.f, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.all_medications_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, ), - ], - ), - ], + ), + ], + ), + ], ), ), ).paddingSymmetrical(0.w, 0.h) @@ -896,7 +907,10 @@ class _MedicalFilePageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), + ("Dr. John Smith Smith Smith") + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: true), ], ) : myAppointmentsVM.patientMyDoctorsList.isEmpty @@ -923,58 +937,58 @@ class _MedicalFilePageState extends State { shrinkWrap: true, itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SizedBox( - // width: 80.w, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.network( - myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, - width: 64.w, - height: 64.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: false, radius: 50.r), - SizedBox(height: 8.h), - Expanded( - child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: false), - ), - ], - ), - ).onPress(() async { - bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( - clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, - projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, - doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, - )); - LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorProfilePage(), - ), - ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), - ), - )); + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SizedBox( + // width: 80.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.w, + height: 64.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false, radius: 50.r), + SizedBox(height: 8.h), + Expanded( + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], + ), + ).onPress(() async { + bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( + clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, + projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, + doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, + )); + LoaderBottomSheet.showLoader(); + await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + ), + )); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), @@ -1083,9 +1097,14 @@ class _MedicalFilePageState extends State { text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", onPressed: () { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: false, + isFullScreen: false); }, backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), borderColor: AppColors.bgGreenColor.withOpacity(0.0), @@ -1282,7 +1301,7 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.blood_sugar_icon, isLargeText: true, iconSize: 36.w, - ).onPress(() {}), + ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)), MedicalFileCard( label: "Blood Pressure".needTranslation, textColor: AppColors.blackColor, @@ -1290,7 +1309,7 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.lab_result_icon, isLargeText: true, iconSize: 36.w, - ).onPress(() {}), + ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)), MedicalFileCard( label: "Weight Tracker".needTranslation, textColor: AppColors.blackColor, @@ -1298,7 +1317,7 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.weight_tracker_icon, isLargeText: true, iconSize: 36.w, - ).onPress(() {}), + ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.weightTracker)), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 16.h), @@ -1547,7 +1566,6 @@ class _MedicalFilePageState extends State { ], ), SizedBox(height: 14.h), - Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h), decoration: BoxDecoration( @@ -1585,7 +1603,6 @@ class _MedicalFilePageState extends State { ), ), SizedBox(height: 8.h), - Align( alignment: AlignmentDirectional.centerEnd, child: Utils.buildSvgWithAssets( @@ -1603,6 +1620,3 @@ class _MedicalFilePageState extends State { ); } } - - - From c11e859df16765fb2b80f60c434714074ba2606b Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 12 Jan 2026 11:40:08 +0300 Subject: [PATCH 25/46] search doctor by name filter. --- .../book_appointments_view_model.dart | 21 ++++ .../search_doctor_by_name.dart | 109 ++++++++++++------ 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 380d2db..aa1467b 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -165,6 +165,27 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + // Sort filtered doctor list by clinic or hospital + void sortFilteredDoctorList(bool sortByClinic) { + isSortByClinic = sortByClinic; + if (sortByClinic) { + // Sort by clinic name + filteredDoctorList.sort((a, b) { + final clinicA = (a.clinicName ?? 'Unknown').toLowerCase(); + final clinicB = (b.clinicName ?? 'Unknown').toLowerCase(); + return clinicA.compareTo(clinicB); + }); + } else { + // Sort by hospital/project name + filteredDoctorList.sort((a, b) { + final hospitalA = (a.projectName ?? 'Unknown').toLowerCase(); + final hospitalB = (b.projectName ?? 'Unknown').toLowerCase(); + return hospitalA.compareTo(hospitalB); + }); + } + notifyListeners(); + } + // Group doctors by clinic and hospital void _groupDoctorsList() { final clinicMap = >{}; diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index d008497..559e1e5 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -43,7 +43,7 @@ class _SearchDoctorByNameState extends State { body: Column( children: [ Expanded( - child: CollapsingListView( + child: CollapsingListView( title: "Choose Doctor".needTranslation, child: SingleChildScrollView( child: Padding( @@ -76,7 +76,7 @@ class _SearchDoctorByNameState extends State { ) : null, onChange: (value) { - // bookAppointmentsViewModel.filterClinics(value!); + // bookAppointmentsViewModel.filterClinics(value!); }, padding: EdgeInsets.symmetric( vertical: ResponsiveExtension(10).h, @@ -89,43 +89,41 @@ class _SearchDoctorByNameState extends State { child: SizedBox( height: 56.h, width: 56.h, - child: DecoratedBox(decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 10.h, - hasShadow: false, - ), - child: Utils.buildSvgWithAssets(icon: AppAssets.ic_filters, - height: 24.h, - width: 24.h, ).paddingAll(16.h).onPress((){ - context.read() + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 10.h, + hasShadow: false, + ), + child: Utils.buildSvgWithAssets( + icon: AppAssets.ic_filters, + height: 24.h, + width: 24.h, + ).paddingAll(16.h).onPress(() { + context.read() ..clearSelection() - ..clearSearchFilters() - ..getFiltersFromDoctorList( - bookAppointmentsViewModel.doctorsList - )..setSelections( - bookAppointmentsViewModel.selectedFacilityForFilters?.toList(), - bookAppointmentsViewModel.selectedRegionForFilters?.toList(), - bookAppointmentsViewModel.selectedClinicForFilters, - bookAppointmentsViewModel.selectedHospitalForFilters, - bookAppointmentsViewModel.applyFilters) ; - Navigator.of(context).push( - PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget - transitionsBuilder: (context, animation, secondaryAnimation, child) { - const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0) - const end = Offset.zero; // End at the original position (y=0.0) - final tween = Tween(begin: begin, end: end); - final offsetAnimation = animation.drive(tween); + ..clearSearchFilters() + ..getFiltersFromDoctorList(bookAppointmentsViewModel.doctorsList) + ..setSelections(bookAppointmentsViewModel.selectedFacilityForFilters?.toList(), bookAppointmentsViewModel.selectedRegionForFilters?.toList(), + bookAppointmentsViewModel.selectedClinicForFilters, bookAppointmentsViewModel.selectedHospitalForFilters, bookAppointmentsViewModel.applyFilters); + Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0) + const end = Offset.zero; // End at the original position (y=0.0) + final tween = Tween(begin: begin, end: end); + final offsetAnimation = animation.drive(tween); - return SlideTransition( - position: offsetAnimation, - child: child, - ); - }, - transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed - ), - ); - }), + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed + ), + ); + }), ), ), ) @@ -136,9 +134,43 @@ class _SearchDoctorByNameState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (bookAppointmentsVM.isDoctorSearchByNameStarted) + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + bookAppointmentsVM.sortFilteredDoctorList(true); + }, + backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: bookAppointmentsVM.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: () { + bookAppointmentsVM.sortFilteredDoctorList(false); + }, + backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(0.h, 0.h), bookAppointmentsVM.isDoctorSearchByNameStarted ? ListView.separated( - padding: EdgeInsets.only(top: 24.h), + padding: EdgeInsets.only(top: 20.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.filteredDoctorList.length, @@ -261,6 +293,7 @@ class _SearchDoctorByNameState extends State { ), ); } + @override void dispose() { bookAppointmentsViewModel.doctorsList.clear(); From ac0d72b3ff8b94d8e72dc6daeb33c5c74f356079 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Mon, 12 Jan 2026 11:41:35 +0300 Subject: [PATCH 26/46] vital sign finalized. --- .../medical_file/medical_file_page.dart | 287 +++++++++--------- .../vital_sign/vital_sign_details_page.dart | 274 +++++++++-------- .../vital_sign/vital_sign_page.dart | 46 +-- 3 files changed, 329 insertions(+), 278 deletions(-) diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 6dde958..3d8d115 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -305,55 +305,64 @@ class _MedicalFilePageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), - Row( - children: [ - LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(0.w, 0.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: VitalSignPage(), - ), - ); - }), + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + Row( + children: [ + LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).paddingSymmetrical(0.w, 0.h).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: VitalSignPage(), + ), + ); + }), + ), SizedBox(height: 16.h), // Make this section dynamic-height (no fixed 160.h) LayoutBuilder( builder: (context, constraints) { if (hmgServicesVM.isVitalSignLoading) { - return _buildVitalSignShimmer(); + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: _buildVitalSignShimmer(), + ); } if (hmgServicesVM.vitalSignList.isEmpty) { - return Container( - padding: EdgeInsets.all(16.w), - width: MediaQuery.of(context).size.width, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "No vital signs recorded yet".needTranslation.toText12(isCenter: true), - ], + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Container( + padding: EdgeInsets.all(16.w), + width: MediaQuery.of(context).size.width, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "No vital signs recorded yet".needTranslation.toText12(isCenter: true), + ], + ), ), ); } // The cards define their own height; measure the first rendered page once _scheduleVitalSignMeasure(); - final double hostHeight = _vitalSignMeasuredHeight ?? (160.h); + final double hostHeight = _vitalSignMeasuredHeight ?? (135.h); return SizedBox( height: hostHeight, @@ -400,7 +409,7 @@ class _MedicalFilePageState extends State { ), ], ], - ).paddingSymmetrical(24.w, 0.0); + ); }), SizedBox(height: 16.h), @@ -1394,23 +1403,23 @@ class _MedicalFilePageState extends State { ).toShimmer(), SizedBox(height: 16.h), // Label shimmer - Container( - width: 70.w, - height: 12.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.r), - ), - ).toShimmer(), - SizedBox(height: 8.h), + // Container( + // width: 70.w, + // height: 12.h, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(4.r), + // ), + // ).toShimmer(), + // SizedBox(height: 8.h), // Value shimmer (larger) - Container( - width: 60.w, - height: 32.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.r), - ), - ).toShimmer(), - SizedBox(height: 12.h), + // Container( + // width: 60.w, + // height: 32.h, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(4.r), + // ), + // ).toShimmer(), + // SizedBox(height: 12.h), // Bottom row with chip and arrow Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1446,62 +1455,66 @@ class _MedicalFilePageState extends State { }) { return [ // Page 1: BMI + Height - Row( - children: [ - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.bmiVital, - label: "BMI", - value: vitalSign.bodyMassIndex?.toString() ?? '--', - unit: '', - status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null, - onTap: onTap, + Padding( + padding: EdgeInsets.only(left: 24.w), + child: Row( + children: [ + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.bmiVital, + label: "BMI", + value: vitalSign.bodyMassIndex?.toString() ?? '--', + unit: '', + status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null, + onTap: onTap, + ), ), - ), - SizedBox(width: 12.w), - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.heightVital, - label: "Height", - value: vitalSign.heightCm?.toString() ?? '--', - unit: 'cm', - status: null, - onTap: onTap, + SizedBox(width: 12.w), + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.heightVital, + label: "Height", + value: vitalSign.heightCm?.toString() ?? '--', + unit: 'cm', + status: null, + onTap: onTap, + ), ), - ), - ], + ], + ), ), // Page 2: Weight + Blood Pressure - Row( - children: [ - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.weightVital, - label: "Weight", - value: vitalSign.weightKg?.toString() ?? '--', - unit: 'kg', - status: vitalSign.weightKg != null ? "Normal" : null, - onTap: onTap, + Padding(padding: EdgeInsets.symmetric(horizontal: 12.w),child: Row( + children: [ + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.weightVital, + label: "Weight", + value: vitalSign.weightKg?.toString() ?? '--', + unit: 'kg', + status: vitalSign.weightKg != null ? "Normal" : null, + onTap: onTap, + ), ), - ), - SizedBox(width: 12.w), - Expanded( - child: _buildVitalSignCard( - icon: AppAssets.bloodPressure, - label: "Blood Pressure", - value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null - ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" - : '--', - unit: '', - status: _getBloodPressureStatus( - systolic: vitalSign.bloodPressureHigher, - diastolic: vitalSign.bloodPressureLower, + SizedBox(width: 12.w), + Expanded( + child: _buildVitalSignCard( + icon: AppAssets.bloodPressure, + label: "Blood Pressure", + value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null + ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" + : '--', + unit: '', + status: _getBloodPressureStatus( + systolic: vitalSign.bloodPressureHigher, + diastolic: vitalSign.bloodPressureLower, + ), + onTap: onTap, ), - onTap: onTap, ), - ), - ], - ), + ], + )), + ]; } @@ -1526,7 +1539,6 @@ class _MedicalFilePageState extends State { return GestureDetector( onTap: onTap, child: Container( - // Same styling used originally for vitals in MedicalFilePage decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 16.r, @@ -1540,15 +1552,15 @@ class _MedicalFilePageState extends State { Row( children: [ Container( - padding: EdgeInsets.all(10.h), + padding: EdgeInsets.all(8.h), decoration: BoxDecoration( color: scheme.iconBg, borderRadius: BorderRadius.circular(12.r), ), child: Utils.buildSvgWithAssets( icon: icon, - width: 20.w, - height: 20.h, + width: 22.w, + height: 22.h, iconColor: scheme.iconFg, fit: BoxFit.contain, ), @@ -1563,55 +1575,56 @@ class _MedicalFilePageState extends State { ], ), SizedBox(height: 14.h), - Container( - padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h), - decoration: BoxDecoration( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bgScaffoldColor, - borderRadius: BorderRadius.circular(10.r), + borderRadius: 10.r, + hasShadow: false, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - value.toText17( - isBold: true, - color: AppColors.textColor, - ), - if (unit.isNotEmpty) ...[ - SizedBox(width: 3.w), - unit.toText12( - color: AppColors.textColor, - fontWeight: FontWeight.w500, + Flexible( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: value.toText17( + isBold: true, + color: AppColors.textColor, + ), ), + if (unit.isNotEmpty) ...[ + SizedBox(width: 3.w), + unit.toText12( + color: AppColors.textColor, + fontWeight: FontWeight.w500, + ), + ], ], - ], + ), ), - if (status != null) + if (status != null) ...[ + SizedBox(width: 4.w), AppCustomChipWidget( labelText: status, backgroundColor: scheme.chipBg, textColor: scheme.chipFg, - ) + ), + ] else - const SizedBox.shrink(), + AppCustomChipWidget( + labelText: "", + backgroundColor: AppColors.bgScaffoldColor, + textColor:null, + ) + ], ), ), - SizedBox(height: 8.h), - Align( - alignment: AlignmentDirectional.centerEnd, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_forward, - width: 18.w, - height: 18.h, - iconColor: AppColors.textColorLight, - fit: BoxFit.contain, - ), - ), ], ), ), diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index fbbea64..f75a71b 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -74,7 +74,7 @@ class _VitalSignDetailsPageState extends State { return SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, children: [ _headerCard( context, @@ -85,16 +85,8 @@ class _VitalSignDetailsPageState extends State { scheme: scheme, latestDate: latest?.vitalSignDate, ), - SizedBox(height: 16.h), - _whatIsThisResultCard(context), - SizedBox(height: 16.h), - _historyCard(context, history: history), - SizedBox(height: 16.h), - - _nextStepsCard(context), - SizedBox(height: 32.h), ], ).paddingAll(24.h), ); @@ -121,65 +113,71 @@ class _VitalSignDetailsPageState extends State { padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - padding: EdgeInsets.all(10.h), - decoration: BoxDecoration( - color: scheme.iconBg, - borderRadius: BorderRadius.circular(12.r), - ), - child: Utils.buildSvgWithAssets( - icon: icon, - width: 20.w, - height: 20.h, - iconColor: scheme.iconFg, - fit: BoxFit.contain, - ), - ), - SizedBox(width: 10.w), - title.toText18(isBold: true, weight: FontWeight.w600), + title.toText28(isBold: true, color: AppColors.textColor, letterSpacing: -1), + + ], ), - if (status != null) - Container( - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), - decoration: BoxDecoration( - color: scheme.chipBg, - borderRadius: BorderRadius.circular(100.r), - ), - child: status.toText11( - color: scheme.chipFg, - weight: FontWeight.w500, - ), + SizedBox(height: 8.h), + (latestDate != null + ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) + : ('Result of --'.needTranslation)) + .toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: valueText.toText28( + isBold: true, + color: scheme.iconFg, + letterSpacing: -2, + ), + ), + SizedBox(width: 4.h), + if (status != null) + Column( + spacing: 6.h, + children: [ + status.toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), + Utils.buildSvgWithAssets( + icon: AppAssets.lab_result_indicator, + width: 21, + height: 23, + iconColor: scheme.iconFg, + ), + ], + ), + ], ), + ), ], ), - SizedBox(height: 10.h), - ( - latestDate != null - ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) - : ('Result of --'.needTranslation) - ).toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), - SizedBox(height: 12.h), - - valueText.toText28(isBold: true, color: AppColors.textColor, letterSpacing: -2), - - if (args.low != null || args.high != null) ...[ - SizedBox(height: 8.h), + if (args.low != null || args.high != null) Text( _referenceText(context), style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, + fontFamily: 'Poppins', color: AppColors.greyTextColor, ), - ) - ] + softWrap: true, + ), ], ), ); @@ -208,21 +206,10 @@ class _VitalSignDetailsPageState extends State { padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, children: [ - 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600), - SizedBox(height: 8.h), - _descriptionText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), - SizedBox(height: 12.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bulb, width: 16.w, height: 16.h, iconColor: AppColors.greyTextColor), - SizedBox(width: 6.w), - Expanded( - child: 'This information is for monitoring and not a diagnosis.'.needTranslation - .toText11(color: AppColors.greyTextColor, weight: FontWeight.w500, maxLine: 3), - ), - ], - ) + 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor), + _descriptionText(context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), ], ), ); @@ -235,34 +222,63 @@ class _VitalSignDetailsPageState extends State { borderRadius: 24.h, hasShadow: true, ), - padding: EdgeInsets.all(16.h), + height: _isGraphVisible + ? 260.h + : (history.length < 3) + ? (history.length * 64) + 80.h + : 260.h, + padding: EdgeInsets.all(15.h), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - 'History flowchart'.needTranslation.toText16(weight: FontWeight.w600), + Text( + _isGraphVisible ? 'History flowchart'.needTranslation : 'History'.needTranslation, + style: TextStyle( + fontSize: 16, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + color: AppColors.textColor, + ), + ), Row( + mainAxisSize: MainAxisSize.min, children: [ - // toggle graph/list similar to lab result details - Utils.buildSvgWithAssets( - icon: _isGraphVisible ? AppAssets.graphIcon : AppAssets.listIcon, - width: 18.w, - height: 18.h, - iconColor: AppColors.greyTextColor, - ).onPress(() { - setState(() { - _isGraphVisible = !_isGraphVisible; - }); - }), - SizedBox(width: 10.w), - Utils.buildSvgWithAssets(icon: AppAssets.calendarGrey, width: 18.w, height: 18.h, iconColor: AppColors.greyTextColor), + Container( + width: 24.h, + height: 24.h, + alignment: Alignment.center, + child: InkWell( + onTap: () { + setState(() { + _isGraphVisible = !_isGraphVisible; + }); + }, + child: Utils.buildSvgWithAssets( + icon: _isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, + width: 24.h, + height: 24.h, + ), + ), + ), + // SizedBox(width: 16.h), + // Container( + // width: 24.h, + // height: 24.h, + // alignment: Alignment.center, + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.ic_date_filter, + // width: 24.h, + // height: 24.h, + // ), + // ), ], ), ], - ), - SizedBox(height: 12.h), + ).paddingOnly(bottom: _isGraphVisible ? 16.h : 24.h), + if (history.isEmpty) Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) else if (_isGraphVisible) @@ -277,47 +293,63 @@ class _VitalSignDetailsPageState extends State { Widget _buildHistoryGraph(List history) { final minY = _minY(history); final maxY = _maxY(history); + final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title); + return CustomGraph( dataPoints: history, makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, showGridLines: true, + showShadow: true, leftLabelInterval: _leftInterval(history), maxY: maxY, minY: minY, maxX: history.length.toDouble() - .75, - horizontalInterval: .1, + horizontalInterval: _leftInterval(history), leftLabelFormatter: (value) { - // Match the lab screen behavior: only show High/Low labels. - final v = double.parse(value.toStringAsFixed(1)); - if (args.high != null && v == args.high) { - return _axisLabel('High'.needTranslation); + // Show labels at interval points + if (args.high != null && (value - args.high!).abs() < 0.1) { + return _axisLabel('High'); } - if (args.low != null && v == args.low) { - return _axisLabel('Low'.needTranslation); + if (args.low != null && (value - args.low!).abs() < 0.1) { + return _axisLabel('Low'); } - return const SizedBox.shrink(); + // Show numeric labels at regular intervals + return _axisLabel(value.toStringAsFixed(0)); }, getDrawingHorizontalLine: (value) { - value = double.parse(value.toStringAsFixed(1)); - if ((args.high != null && value == args.high) || (args.low != null && value == args.low)) { + // Draw reference lines for high/low bounds + if (args.high != null && (value - args.high!).abs() < 0.1) { return FlLine( - color: AppColors.bgGreenColor.withValues(alpha: 0.6), + color: AppColors.bgGreenColor.withOpacity(0.2), strokeWidth: 1, + dashArray: [5, 5], ); } - return const FlLine(color: Colors.transparent, strokeWidth: 1); - }, - graphColor: AppColors.blackColor, - graphShadowColor: Colors.transparent, - graphGridColor: AppColors.graphGridColor.withValues(alpha: .4), - bottomLabelFormatter: (value, data) { - if (data.isEmpty) return const SizedBox.shrink(); - if (value == 0) return _bottomLabel(data[value.toInt()].label); - if (value == data.length - 1) return _bottomLabel(data[value.toInt()].label); - if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label); - return const SizedBox.shrink(); + if (args.low != null && (value - args.low!).abs() < 0.1) { + return FlLine( + color: AppColors.bgGreenColor.withOpacity(0.2), + strokeWidth: 1, + dashArray: [5, 5], + ); + } + // Draw grid lines at intervals + return FlLine( + color: AppColors.bgGreenColor.withOpacity(0.2), + strokeWidth: 1, + dashArray: [5, 5], + ); }, + graphColor: AppColors.bgGreenColor, + graphShadowColor: AppColors.lightGreenColor.withOpacity(.4), + graphGridColor: scheme.iconFg, + bottomLabelFormatter: (value, data) { + if (data.isEmpty) return const SizedBox.shrink(); + if (value == 0) return _bottomLabel(data[value.toInt()].label); + if (value == data.length - 1) return _bottomLabel(data[value.toInt()].label, isLast: true); + if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label); + return const SizedBox.shrink(); + }, rangeAnnotations: _rangeAnnotations(history), minX: (history.length == 1) ? null : -.2, scrollDirection: Axis.horizontal, @@ -325,6 +357,7 @@ class _VitalSignDetailsPageState extends State { ); } + Widget _buildHistoryList(BuildContext context, List history) { final items = history.reversed.toList(); final height = items.length < 3 ? items.length * 64.0 : 180.h; @@ -393,7 +426,7 @@ class _VitalSignDetailsPageState extends State { HorizontalRangeAnnotation( y1: minY, y2: args.low!, - color: AppColors.highAndLow.withValues(alpha: 0.05), + color: AppColors.highAndLow.withOpacity(0.05), ), ); } @@ -403,7 +436,7 @@ class _VitalSignDetailsPageState extends State { HorizontalRangeAnnotation( y1: args.low!, y2: args.high!, - color: AppColors.bgGreenColor.withValues(alpha: 0.05), + color: AppColors.bgGreenColor.withOpacity(0.05), ), ); } @@ -413,7 +446,7 @@ class _VitalSignDetailsPageState extends State { HorizontalRangeAnnotation( y1: args.high!, y2: maxY, - color: AppColors.criticalLowAndHigh.withValues(alpha: 0.05), + color: AppColors.criticalLowAndHigh.withOpacity(0.05), ), ); } @@ -447,11 +480,14 @@ class _VitalSignDetailsPageState extends State { case VitalSignMetric.respiratoryRate: return _toDouble(v.respirationBeatPerMinute); case VitalSignMetric.bloodPressure: - // Graph only systolic for now (simple single-series). + // Graph only systolic for now (simple single-series). return _toDouble(v.bloodPressureHigher); } } + const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + double index = 0; for (final v in sorted) { final mv = metricValue(v); @@ -459,7 +495,7 @@ class _VitalSignDetailsPageState extends State { if (mv == 0) continue; final dt = v.vitalSignDate ?? DateTime.now(); - final label = '${dt.day}/${dt.month}'; + final label = '${monthNames[dt.month - 1]}, ${dt.year}'; points.add( DataPoint( @@ -603,18 +639,14 @@ class _VitalSignDetailsPageState extends State { ); } - Widget _bottomLabel(String label) { + Widget _bottomLabel(String label, {bool isLast = false}) { return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - label, - style: TextStyle( - fontSize: 8.f, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - color: AppColors.labelTextColor, - ), + padding: EdgeInsets.only( + top: 8.0, + right: isLast ? 16.h : 0, ), + child: label.toText8(fontWeight: FontWeight.w500), ); } + } diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index d8b6d7e..95ff3e5 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -178,7 +178,7 @@ class _VitalSignPageState extends State { children: [ // Body anatomy image with Heart Rate card overlaid at bottom SizedBox( - height: 480.h, + height: 420.h, width: double.infinity, child: Stack( clipBehavior: Clip.none, @@ -196,7 +196,7 @@ class _VitalSignPageState extends State { Align( alignment: Alignment.bottomCenter, child: SizedBox( - height: 420.h, + height: 480.h, child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Container( @@ -245,7 +245,7 @@ class _VitalSignPageState extends State { ], ), ), - SizedBox(height: 12.h), + SizedBox(height: 12.h), // Respiratory rate Card _buildVitalSignCard( @@ -308,15 +308,15 @@ class _VitalSignPageState extends State { Row( children: [ Container( - padding: EdgeInsets.all(10.h), + padding: EdgeInsets.all(8.h), decoration: BoxDecoration( color: scheme.iconBg, borderRadius: BorderRadius.circular(12.r), ), child: Utils.buildSvgWithAssets( icon: icon, - width: 20.w, - height: 20.h, + width: 22.w, + height: 22.h, iconColor: scheme.iconFg, fit: BoxFit.contain, ), @@ -332,10 +332,15 @@ class _VitalSignPageState extends State { ), SizedBox(height: 14.h), Container( - padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h), - decoration: BoxDecoration( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.h), + // decoration: BoxDecoration( + // color: AppColors.bgScaffoldColor, + // borderRadius: BorderRadius.circular(10.r), + // ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bgScaffoldColor, - borderRadius: BorderRadius.circular(10.r), + borderRadius: 10.r, + hasShadow: false, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -361,23 +366,24 @@ class _VitalSignPageState extends State { labelText: status, backgroundColor: scheme.chipBg, textColor: scheme.chipFg, + ) else const SizedBox.shrink(), ], ), ), - SizedBox(height: 8.h), - Align( - alignment: AlignmentDirectional.centerEnd, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_forward, - width: 18.w, - height: 18.h, - iconColor: AppColors.textColorLight, - fit: BoxFit.contain, - ), - ), + // SizedBox(height: 8.h), + // Align( + // alignment: AlignmentDirectional.centerEnd, + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.arrow_forward, + // width: 18.w, + // height: 18.h, + // iconColor: AppColors.textColorLight, + // fit: BoxFit.contain, + // ), + // ), ], ), ), From 0514716910eab59caa0f5fcba914f9a608d76333 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Mon, 12 Jan 2026 11:48:05 +0300 Subject: [PATCH 27/46] no message --- lib/presentation/vital_sign/vital_sign_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 95ff3e5..fbf9fc6 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -196,7 +196,7 @@ class _VitalSignPageState extends State { Align( alignment: Alignment.bottomCenter, child: SizedBox( - height: 480.h, + height: 460.h, child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Container( From 8ee0e3b4ea26f4f7efa31ced4f537ea39c9a3a6b Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Mon, 12 Jan 2026 12:10:08 +0300 Subject: [PATCH 28/46] no message --- lib/core/dependencies.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 582b795..012bd34 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -265,7 +265,8 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + //commenting this because its already define there was on run time error because of this. + // getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); getIt.registerLazySingleton(() => MyInvoicesViewModel( From d848291930d665447a9fc67988d2baa60ef6f0ec Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 12 Jan 2026 12:18:33 +0300 Subject: [PATCH 29/46] updates --- lib/core/dependencies.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 2c83a4f..de078b8 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -273,11 +273,7 @@ class AppDependencies { getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton(() => MyInvoicesViewModel( - myInvoicesRepo: getIt(), - errorHandlerService: getIt(), - navServices: getIt(), - )); + getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); } } From 44ce353dac9cde87772ff3c668ae2bde43c0c6fd Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 12 Jan 2026 15:18:09 +0300 Subject: [PATCH 30/46] updates --- android/app/build.gradle.kts | 1 + lib/core/utils/utils.dart | 14 +- .../hospital_selection_view_model.dart | 36 ++- .../hmg_services/services_page.dart | 229 +++++++++++------- lib/widgets/appbar/app_bar_widget.dart | 12 +- 5 files changed, 188 insertions(+), 104 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2987d3b..5d74c4d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -160,6 +160,7 @@ dependencies { annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") implementation("com.mapbox.maps:android:11.5.0") + implementation("com.mapbox.mapboxsdk:mapbox-sdk-turf:7.3.1") // implementation("com.mapbox.maps:android:11.4.0") // AARs diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index a26c4a5..8978fcd 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -55,11 +55,12 @@ class Utils { "IsActive": true, "IsHmg": true, "IsVidaPlus": false, - "Latitude": "24.8113774", - "Longitude": "46.6239813", + "Latitude": "24.8111548", + "Longitude": "46.6217912", "MainProjectID": 130, "ProjectOutSA": false, - "UsingInDoctorApp": false + "UsingInDoctorApp": false, + "IsHMC": false },{ "Desciption": "Jeddah Fayhaa Hospital", "DesciptionN": "مستشفى جدة الفيحاء", @@ -75,11 +76,12 @@ class Utils { "IsActive": true, "IsHmg": true, "IsVidaPlus": false, - "Latitude": "24.8113774", - "Longitude": "46.6239813", + "Latitude": "21.5086703", + "Longitude": "39.2121855", "MainProjectID": 130, "ProjectOutSA": false, - "UsingInDoctorApp": false + "UsingInDoctorApp": false, + "IsHMC": false } ]; diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index 59c881c..c0cab2a 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; @@ -17,20 +19,32 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { int hmgCount = 0; TextEditingController searchController = TextEditingController(); final AppState appState; + LocationUtils locationUtils = getIt.get(); HospitalSelectionBottomSheetViewModel(this.appState) { - Utils.navigationProjectsList.forEach((element) { - HospitalsModel model = HospitalsModel.fromJson(element); - if (model.isHMC == true) { - hmcHospitalList.add(model); - } else { - hmgHospitalList.add(model); - } - listOfData.add(model); + locationUtils.getCurrentLocation(onSuccess: (value) { + Utils.navigationProjectsList.forEach((element) { + HospitalsModel model = HospitalsModel.fromJson(element); + + double dist = Utils.distance(value.latitude, value.longitude, double.parse(model.latitude!), double.parse(model.longitude!)).ceilToDouble(); + print(dist); + model.distanceInKilometers = dist; + + if (model.isHMC == true) { + hmcHospitalList.add(model); + } else { + hmgHospitalList.add(model); + } + listOfData.add(model); + }); + hmgCount = hmgHospitalList.length; + hmcCount = hmcHospitalList.length; + + listOfData.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + hmgHospitalList.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + + getDisplayList(); }); - hmgCount = hmgHospitalList.length; - hmcCount = hmcHospitalList.length; - getDisplayList(); } getDisplayList() { diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 4d74bc7..ab0cced 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -4,15 +4,18 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -36,6 +39,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import '../../core/dependencies.dart' show getIt; +import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; class ServicesPage extends StatelessWidget { ServicesPage({super.key}); @@ -44,27 +48,24 @@ class ServicesPage extends StatelessWidget { late MedicalFileViewModel medicalFileViewModel; late final List hmgServices = [ - HmgServicesComponentModel( - 11, - "Emergency Services".needTranslation, - "".needTranslation, - AppAssets.emergency_services_icon, - bgColor: AppColors.primaryRedColor, - true, - route: null, onTap: () { - getIt.get().flushData(); - getIt.get().getTransportationOrders( - showLoader: false, - ); - getIt.get().getRRTOrders( - showLoader: false, - ); - Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( - CustomPageRoute( - page: EmergencyServicesPage(), - settings: const RouteSettings(name: '/EmergencyServicesPage'), - ), - ); + HmgServicesComponentModel(11, "Emergency Services".needTranslation, "".needTranslation, AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().flushData(); + getIt.get().getTransportationOrders( + showLoader: false, + ); + getIt.get().getRRTOrders( + showLoader: false, + ); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( + CustomPageRoute( + page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } }), HmgServicesComponentModel( 11, @@ -75,15 +76,13 @@ class ServicesPage extends StatelessWidget { true, route: AppRoutes.bookAppointmentPage, ), - HmgServicesComponentModel( - 5, - "Complete Checkup".needTranslation, - "".needTranslation, - AppAssets.comprehensiveCheckup, - bgColor: AppColors.bgGreenColor, - true, - route: AppRoutes.comprehensiveCheckupPage, - ), + HmgServicesComponentModel(5, "Complete Checkup".needTranslation, "".needTranslation, AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); + } else { + await getIt.get().onLoginPressed(); + } + }), HmgServicesComponentModel( 11, "Indoor Navigation".needTranslation, @@ -92,17 +91,51 @@ class ServicesPage extends StatelessWidget { bgColor: Color(0xff45A2F8), true, route: null, - onTap: () {}, + onTap: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + GetIt.instance().navigatorKey.currentContext!, + child: ChangeNotifierProvider( + create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + vm.openPenguin(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + GetIt.instance().navigatorKey.currentContext!.read().clearSearchText(); + }, + ); + }, ), HmgServicesComponentModel( - 11, - "E-Referral Services".needTranslation, - "".needTranslation, - AppAssets.eReferral, - bgColor: AppColors.eReferralCardColor, - true, - route: AppRoutes.eReferralPage, - ), + 11, "E-Referral Services".needTranslation, "".needTranslation, AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.eReferralPage); + } else { + await getIt.get().onLoginPressed(); + } + }), HmgServicesComponentModel( 3, "Blood Donation".needTranslation, @@ -180,7 +213,14 @@ class ServicesPage extends StatelessWidget { AppAssets.general_health, bgColor: AppColors.whiteColor, true, - route: AppRoutes.healthTrackersPage, + route: null, + onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.healthTrackersPage); + } else { + await getIt.get().onLoginPressed(); + } + }, ), HmgServicesComponentModel( 11, @@ -191,24 +231,28 @@ class ServicesPage extends StatelessWidget { true, route: null, // Set to null since we handle navigation in onTap onTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); - final waterMonitorVM = getIt.get(); - final context = getIt.get().navigatorKey.currentContext!; - await waterMonitorVM.fetchUserDetailsForMonitoring( - onSuccess: (userDetail) { - LoaderBottomSheet.hideLoader(); - if (userDetail == null) { - waterMonitorVM.populateFromAuthenticatedUser(); - context.navigateWithName(AppRoutes.waterMonitorSettingsPage); - } else { + if (getIt.get().isAuthenticated) { + LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); + final waterMonitorVM = getIt.get(); + final context = getIt.get().navigatorKey.currentContext!; + await waterMonitorVM.fetchUserDetailsForMonitoring( + onSuccess: (userDetail) { + LoaderBottomSheet.hideLoader(); + if (userDetail == null) { + waterMonitorVM.populateFromAuthenticatedUser(); + context.navigateWithName(AppRoutes.waterMonitorSettingsPage); + } else { + context.navigateWithName(AppRoutes.waterConsumptionPage); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); context.navigateWithName(AppRoutes.waterConsumptionPage); - } - }, - onError: (error) { - LoaderBottomSheet.hideLoader(); - context.navigateWithName(AppRoutes.waterConsumptionPage); - }, - ); + }, + ); + } else { + await getIt.get().onLoginPressed(); + } }, ), HmgServicesComponentModel( @@ -236,7 +280,14 @@ class ServicesPage extends StatelessWidget { AppAssets.smartwatch_icon, bgColor: AppColors.whiteColor, true, - route: AppRoutes.smartWatches, + route: null, + onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.smartWatches); + } else { + await getIt.get().onLoginPressed(); + } + }, // route: AppRoutes.huaweiHealthExample, ), ]; @@ -329,14 +380,17 @@ class ServicesPage extends StatelessWidget { ], ), Spacer(), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }), + getIt.get().isAuthenticated + ? Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) + : "Login to view your wallet balance".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), - CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, iconSize: 16.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, @@ -350,10 +404,15 @@ class ServicesPage extends StatelessWidget { onPressed: () { Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); }, - ), + ) + : SizedBox.shrink(), ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + } else { + await getIt.get().onLoginPressed(); + } }), ), ), @@ -375,13 +434,14 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), - "Medical Files".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.medicalFile.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), ], ), Spacer(), - Wrap( - spacing: -8.h, + getIt.get().isAuthenticated + ? Wrap( + spacing: -8.h, // runSpacing: 0.h, children: [ Utils.buildImgWithAssets( @@ -409,10 +469,12 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), ], - ), + ) + : "Login to view your medical file".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), - CustomButton( - height: 40.h, + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, icon: AppAssets.add_icon, iconSize: 16.w, iconColor: AppColors.primaryRedColor, @@ -434,14 +496,19 @@ class ServicesPage extends StatelessWidget { medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); }, - ), + ) + : SizedBox.shrink(), ], - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - ), - ); + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } }), ), ), diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart index b3bc3eb..942a93f 100644 --- a/lib/widgets/appbar/app_bar_widget.dart +++ b/lib/widgets/appbar/app_bar_widget.dart @@ -42,15 +42,15 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { ? RotatedBox( quarterTurns: 90, child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32.w, - height: 32.h, + icon: AppAssets.forward_top_nav_icon, + width: 24.w, + height: 24.h, ), ) : Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32.w, - height: 32.h, + icon: AppAssets.forward_top_nav_icon, + width: 24.w, + height: 24.h, ), ), ), From 609ae6ab83544391a04dab6d1e10d90045cd1a01 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 12 Jan 2026 15:32:19 +0300 Subject: [PATCH 31/46] updates --- lib/core/dependencies.dart | 8 -------- lib/presentation/hmg_services/services_page.dart | 1 - .../home/widgets/small_service_card.dart | 13 +++++++++++++ lib/presentation/parking/paking_page.dart | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index e4113f5..ebd09dd 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -304,13 +304,5 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); - } } diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 811b343..5c028db 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -606,7 +606,6 @@ class ServicesPage extends StatelessWidget { ), ), ); - }), ), ), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index e74100c..49db8bf 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -7,6 +7,7 @@ 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/hospital/hospital_selection_view_model.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; @@ -15,6 +16,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; +import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -151,6 +153,17 @@ class SmallServiceCard extends StatelessWidget { ), ); break; + case "parking_guide": + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), + ), + ), + ); + break; default: // Handle unknown service break; diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index cd1e8bc..a82703b 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -55,7 +55,7 @@ class _ParkingPageState extends State { children: [ Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.symmetric(horizontal: 24.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ From 6725ab028d5ec69dd9d7b006f90508cec054b87b Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 12 Jan 2026 15:55:41 +0300 Subject: [PATCH 32/46] doctor search filter design update & switch user bug fix. --- .../authentication_view_model.dart | 11 +- .../book_appointments_view_model.dart | 37 ++++- .../my_appointments_view_model.dart | 1 + .../search_doctor_by_name.dart | 144 ++++++++++++++---- .../book_appointment/select_doctor_page.dart | 80 +++++----- .../book_appointment/widgets/doctor_card.dart | 2 +- 6 files changed, 203 insertions(+), 72 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index b393772..6379456 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -246,7 +246,6 @@ class AuthenticationViewModel extends ChangeNotifier { } Future _handleNewImeiRegistration() async { - await selectDeviceImei(onSuccess: (dynamic respData) async { try { if (respData != null) { @@ -461,6 +460,12 @@ class AuthenticationViewModel extends ChangeNotifier { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); MyAppointmentsViewModel myAppointmentsVM = getIt(); + if (isSwitchUser && _appState.getSuperUserID == null) { + nationalIdController.text = responseID.toString(); + }else if( isSwitchUser && _appState.getSuperUserID != null){ + nationalIdController.text = _appState.getSuperUserID.toString(); + } + final request = RequestUtils.getCommonRequestWelcome( phoneNumber: phoneNumberController.text, otpTypeEnum: otpTypeEnum, @@ -761,14 +766,14 @@ class AuthenticationViewModel extends ChangeNotifier { phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; - nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + nationalIdController.text = _appState.getAuthenticatedUser()!.patientIdentificationNo!; onSuccess(); } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && _appState.getAuthenticatedUser() != null) { phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; - nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + nationalIdController.text = _appState.getAuthenticatedUser()!.patientIdentificationNo!; onSuccess(); } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index aa1467b..a225c1d 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -53,6 +53,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { int? calculationID = 0; bool isSortByClinic = true; + // Accordion expansion state + int? expandedGroupIndex; + int initialSlotDuration = 0; bool isNearestAppointmentSelected = false; @@ -158,13 +161,28 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isBodyPartsLoading = false; int duration = 0; - setIsSortByClinic(bool value) { isSortByClinic = value; doctorsListGrouped = isSortByClinic ? doctorsListByClinic : doctorsListByHospital; notifyListeners(); } + // Toggle accordion expansion + void toggleGroupExpansion(int index) { + if (expandedGroupIndex == index) { + expandedGroupIndex = null; + } else { + expandedGroupIndex = index; + } + notifyListeners(); + } + + // Reset accordion expansion + void resetGroupExpansion() { + expandedGroupIndex = null; + notifyListeners(); + } + // Sort filtered doctor list by clinic or hospital void sortFilteredDoctorList(bool sortByClinic) { isSortByClinic = sortByClinic; @@ -186,6 +204,22 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + // Group filtered doctors list for accordion display + List> getGroupedFilteredDoctorsList() { + final clinicMap = >{}; + final hospitalMap = >{}; + + for (var doctor in filteredDoctorList) { + final clinicKey = (doctor.clinicName ?? 'Unknown').trim(); + clinicMap.putIfAbsent(clinicKey, () => []).add(doctor); + + final hospitalKey = (doctor.projectName ?? 'Unknown').trim(); + hospitalMap.putIfAbsent(hospitalKey, () => []).add(doctor); + } + + return isSortByClinic ? clinicMap.values.toList() : hospitalMap.values.toList(); + } + // Group doctors by clinic and hospital void _groupDoctorsList() { final clinicMap = >{}; @@ -237,6 +271,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { isDoctorProfileLoading = true; isLiveCareSchedule = false; currentlySelectedHospitalFromRegionFlow = null; + expandedGroupIndex = null; // Reset accordion state clinicsList.clear(); doctorsList.clear(); liveCareClinicsList.clear(); diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 9bd48ec..3934bf7 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -726,6 +726,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientAppointmentQueueDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + //TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State; isAppointmentQueueDetailsLoading = true; notifyListeners(); final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails( diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 559e1e5..eb6d06f 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -34,6 +34,14 @@ class _SearchDoctorByNameState extends State { TextEditingController searchEditingController = TextEditingController(); FocusNode textFocusNode = FocusNode(); late BookAppointmentsViewModel bookAppointmentsViewModel; + late ScrollController _scrollController; + final Map _itemKeys = {}; + + @override + void initState() { + _scrollController = ScrollController(); + super.initState(); + } @override Widget build(BuildContext context) { @@ -173,8 +181,11 @@ class _SearchDoctorByNameState extends State { padding: EdgeInsets.only(top: 20.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.filteredDoctorList.length, + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.getGroupedFilteredDoctorsList().length, itemBuilder: (context, index) { + final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + final groupedDoctors = bookAppointmentsVM.getGroupedFilteredDoctorsList(); + return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( doctorsListResponseModel: DoctorsListResponseModel(), @@ -188,35 +199,113 @@ class _SearchDoctorByNameState extends State { verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( + key: _itemKeys.putIfAbsent(index, () => GlobalKey()), duration: Duration(milliseconds: 300), curve: Curves.easeInOut, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: DoctorCard( - doctorsListResponseModel: bookAppointmentsVM.filteredDoctorList[index], - isLoading: false, - bookAppointmentsViewModel: bookAppointmentsViewModel, - ).onPress(() async { - bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]); - // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); - LoaderBottomSheet.showLoader(); - await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorProfilePage(), - ), - ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), + child: InkWell( + onTap: () { + bookAppointmentsVM.toggleGroupExpansion(index); + // After rebuild, ensure the expanded item is visible + WidgetsBinding.instance.addPostFrameCallback((_) { + final key = _itemKeys[index]; + if (key != null && key.currentContext != null && bookAppointmentsVM.expandedGroupIndex == index) { + Scrollable.ensureVisible( + key.currentContext!, + duration: Duration(milliseconds: 350), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } + }); + }, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: "${groupedDoctors[index].length} ${'doctors'.needTranslation}", + 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: 30.h), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + // Clinic/Hospital name as group title + Text( + bookAppointmentsVM.isSortByClinic + ? (groupedDoctors[index].first.clinicName ?? 'Unknown') + : (groupedDoctors[index].first.projectName ?? 'Unknown'), + style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + // Expanded content - list of doctors in this group + AnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.only(top: 12.h), + child: Column( + children: groupedDoctors[index].asMap().entries.map((entry) { + final doctorIndex = entry.key; + final doctor = entry.value; + final isLastDoctor = doctorIndex == groupedDoctors[index].length - 1; + + return Column( + children: [ + DoctorCard( + doctorsListResponseModel: doctor, + isLoading: false, + bookAppointmentsViewModel: bookAppointmentsViewModel, + ).onPress(() async { + bookAppointmentsVM.setSelectedDoctor(doctor); + LoaderBottomSheet.showLoader(); + await bookAppointmentsVM.getDoctorProfile( + onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), + if (!isLastDoctor) + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + ], + ); + }).toList(), + ), + ) + : SizedBox.shrink(), + ), + ], + ), + ), + ), ), ), ), @@ -296,6 +385,7 @@ class _SearchDoctorByNameState extends State { @override void dispose() { + _scrollController.dispose(); bookAppointmentsViewModel.doctorsList.clear(); super.dispose(); } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 569e8ac..28b0f61 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -33,7 +33,6 @@ class SelectDoctorPage extends StatefulWidget { class _SelectDoctorPageState extends State { TextEditingController searchEditingController = TextEditingController(); - int? expandedIndex; FocusNode textFocusNode = FocusNode(); @@ -170,8 +169,7 @@ class _SelectDoctorPageState extends State { ), ], ).paddingSymmetrical(0.h, 0.h), - if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) - SizedBox(height: 16.h), + if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) SizedBox(height: 16.h), Row( mainAxisSize: MainAxisSize.max, children: [ @@ -190,7 +188,6 @@ class _SelectDoctorPageState extends State { value: bookAppointmentsVM.isNearestAppointmentSelected, onChanged: (newValue) async { bookAppointmentsVM.setIsNearestAppointmentSelected(newValue); - }, ), ], @@ -199,11 +196,9 @@ class _SelectDoctorPageState extends State { padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isDoctorsListLoading - ? 5 - : (bookAppointmentsVM.doctorsListGrouped.isNotEmpty ? bookAppointmentsVM.doctorsListGrouped.length : 1), + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.doctorsListGrouped.isNotEmpty ? bookAppointmentsVM.doctorsListGrouped.length : 1), itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; + final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( doctorsListResponseModel: DoctorsListResponseModel(), @@ -225,13 +220,11 @@ class _SelectDoctorPageState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: InkWell( onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); + bookAppointmentsVM.toggleGroupExpansion(index); // After rebuild, ensure the expanded item is visible WidgetsBinding.instance.addPostFrameCallback((_) { final key = _itemKeys[index]; - if (key != null && key.currentContext != null && expandedIndex == index) { + if (key != null && key.currentContext != null && bookAppointmentsVM.expandedGroupIndex == index) { Scrollable.ensureVisible( key.currentContext!, duration: Duration(milliseconds: 350), @@ -282,34 +275,41 @@ class _SelectDoctorPageState extends State { key: ValueKey(index), padding: EdgeInsets.only(top: 12.h), child: Column( - children: bookAppointmentsVM.doctorsListGrouped[index].map((doctor) { - return Container( - margin: EdgeInsets.only(bottom: 12.h), - child: DoctorCard( - doctorsListResponseModel: doctor, - isLoading: false, - bookAppointmentsViewModel: bookAppointmentsViewModel, - ).onPress(() async { - bookAppointmentsVM.setSelectedDoctor(doctor); - LoaderBottomSheet.showLoader(); - await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorProfilePage(), - ), - ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), + children: bookAppointmentsVM.doctorsListGrouped[index].asMap().entries.map((entry) { + final doctorIndex = entry.key; + final doctor = entry.value; + final isLastDoctor = doctorIndex == bookAppointmentsVM.doctorsListGrouped[index].length - 1; + + return Column( + children: [ + DoctorCard( + doctorsListResponseModel: doctor, + isLoading: false, + bookAppointmentsViewModel: bookAppointmentsViewModel, + ).onPress(() async { + bookAppointmentsVM.setSelectedDoctor(doctor); + LoaderBottomSheet.showLoader(); + await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + if (!isLastDoctor) + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + ], ); }).toList(), ), diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index ffe26ff..0f30c5f 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -38,7 +38,7 @@ class DoctorCard extends StatelessWidget { hasShadow: false, ), child: Padding( - padding: EdgeInsets.all(14.h), + padding: EdgeInsets.only(top: 14.h,bottom: 20.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ From 42222804fdacf7bc0bbaa06a7af2d18a1ae8e3cc Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Mon, 12 Jan 2026 17:25:34 +0300 Subject: [PATCH 33/46] fix graph dot and bp graph. --- .../vital_sign/vital_sign_details_page.dart | 124 ++++++++++++++---- lib/widgets/graph/custom_graph.dart | 37 ++++-- 2 files changed, 126 insertions(+), 35 deletions(-) diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index f75a71b..f632502 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -216,6 +216,14 @@ class _VitalSignDetailsPageState extends State { } Widget _historyCard(BuildContext context, {required List history}) { + // For blood pressure, we need both systolic and diastolic series + List? secondaryHistory; + if (args.metric == VitalSignMetric.bloodPressure) { + secondaryHistory = _buildBloodPressureDiastolicSeries( + context.read().vitalSignList, + ); + } + return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -282,7 +290,7 @@ class _VitalSignDetailsPageState extends State { if (history.isEmpty) Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) else if (_isGraphVisible) - _buildHistoryGraph(history) + _buildHistoryGraph(history, secondaryHistory: secondaryHistory) else _buildHistoryList(context, history), ], @@ -290,31 +298,25 @@ class _VitalSignDetailsPageState extends State { ); } - Widget _buildHistoryGraph(List history) { - final minY = _minY(history); - final maxY = _maxY(history); + Widget _buildHistoryGraph(List history, {List? secondaryHistory}) { + final minY = _minY(history, secondaryHistory: secondaryHistory); + final maxY = _maxY(history, secondaryHistory: secondaryHistory); final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title); return CustomGraph( dataPoints: history, + secondaryDataPoints: secondaryHistory, makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, showGridLines: true, showShadow: true, - leftLabelInterval: _leftInterval(history), + leftLabelInterval: _leftInterval(history, secondaryHistory: secondaryHistory), maxY: maxY, minY: minY, maxX: history.length.toDouble() - .75, - horizontalInterval: _leftInterval(history), + horizontalInterval: _leftInterval(history, secondaryHistory: secondaryHistory), leftLabelFormatter: (value) { - // Show labels at interval points - if (args.high != null && (value - args.high!).abs() < 0.1) { - return _axisLabel('High'); - } - if (args.low != null && (value - args.low!).abs() < 0.1) { - return _axisLabel('Low'); - } - // Show numeric labels at regular intervals + // Show only numeric labels at regular intervals return _axisLabel(value.toStringAsFixed(0)); }, getDrawingHorizontalLine: (value) { @@ -341,6 +343,7 @@ class _VitalSignDetailsPageState extends State { ); }, graphColor: AppColors.bgGreenColor, + secondaryGraphColor: AppColors.blueColor, graphShadowColor: AppColors.lightGreenColor.withOpacity(.4), graphGridColor: scheme.iconFg, bottomLabelFormatter: (value, data) { @@ -350,7 +353,7 @@ class _VitalSignDetailsPageState extends State { if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label); return const SizedBox.shrink(); }, - rangeAnnotations: _rangeAnnotations(history), + rangeAnnotations: _rangeAnnotations(history, secondaryHistory: secondaryHistory), minX: (history.length == 1) ? null : -.2, scrollDirection: Axis.horizontal, height: 180.h, @@ -361,6 +364,15 @@ class _VitalSignDetailsPageState extends State { Widget _buildHistoryList(BuildContext context, List history) { final items = history.reversed.toList(); final height = items.length < 3 ? items.length * 64.0 : 180.h; + + // Get diastolic values if this is blood pressure + List? secondaryItems; + if (args.metric == VitalSignMetric.bloodPressure) { + final viewModel = context.read(); + final secondaryHistory = _buildBloodPressureDiastolicSeries(viewModel.vitalSignList); + secondaryItems = secondaryHistory.reversed.toList(); + } + return SizedBox( height: height, child: ListView.separated( @@ -372,13 +384,25 @@ class _VitalSignDetailsPageState extends State { ), itemBuilder: (context, index) { final dp = items[index]; + + // Build the value text based on metric type + String valueText; + if (args.metric == VitalSignMetric.bloodPressure && secondaryItems != null && index < secondaryItems.length) { + // Show systolic/diastolic for blood pressure + final diastolic = secondaryItems[index]; + valueText = '${dp.actualValue}/${diastolic.actualValue} ${dp.unitOfMeasurement ?? ''}'; + } else { + // Show single value for other metrics + valueText = '${dp.actualValue} ${dp.unitOfMeasurement ?? ''}'; + } + return Padding( padding: EdgeInsets.symmetric(vertical: 12.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ dp.displayTime.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - ('${dp.actualValue} ${dp.unitOfMeasurement ?? ''}').toText12( + valueText.toText12( color: AppColors.textColor, fontWeight: FontWeight.w600, ), @@ -390,34 +414,46 @@ class _VitalSignDetailsPageState extends State { ); } - double _minY(List points) { + double _minY(List points, {List? secondaryHistory}) { // IMPORTANT: y-axis uses actual numeric values (from actualValue). final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + + // Include secondary data points if provided (for blood pressure) + if (secondaryHistory != null && secondaryHistory.isNotEmpty) { + values.addAll(secondaryHistory.map((e) => double.tryParse(e.actualValue) ?? 0)); + } + final min = values.reduce((a, b) => a < b ? a : b); final double boundLow = args.low ?? min; return (min < boundLow ? min : boundLow) - 1; } - double _maxY(List points) { + double _maxY(List points, {List? secondaryHistory}) { // IMPORTANT: y-axis uses actual numeric values (from actualValue). final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + + // Include secondary data points if provided (for blood pressure) + if (secondaryHistory != null && secondaryHistory.isNotEmpty) { + values.addAll(secondaryHistory.map((e) => double.tryParse(e.actualValue) ?? 0)); + } + final max = values.reduce((a, b) => a > b ? a : b); final double boundHigh = args.high ?? max; return (max > boundHigh ? max : boundHigh) + 1; } - double _leftInterval(List points) { + double _leftInterval(List points, {List? secondaryHistory}) { // Keep it stable; graph will mostly show just two labels. - final range = (_maxY(points) - _minY(points)).abs(); + final range = (_maxY(points, secondaryHistory: secondaryHistory) - _minY(points, secondaryHistory: secondaryHistory)).abs(); if (range <= 0) return 1; return (range / 4).clamp(1, 20); } - RangeAnnotations? _rangeAnnotations(List points) { + RangeAnnotations? _rangeAnnotations(List points, {List? secondaryHistory}) { if (args.low == null && args.high == null) return null; - final minY = _minY(points); - final maxY = _maxY(points); + final minY = _minY(points, secondaryHistory: secondaryHistory); + final maxY = _maxY(points, secondaryHistory: secondaryHistory); final List ranges = []; @@ -480,7 +516,7 @@ class _VitalSignDetailsPageState extends State { case VitalSignMetric.respiratoryRate: return _toDouble(v.respirationBeatPerMinute); case VitalSignMetric.bloodPressure: - // Graph only systolic for now (simple single-series). + // Graph systolic for primary series return _toDouble(v.bloodPressureHigher); } } @@ -513,6 +549,46 @@ class _VitalSignDetailsPageState extends State { return points; } + /// Build diastolic blood pressure series for dual-line graph + List _buildBloodPressureDiastolicSeries(List vitals) { + final List points = []; + + // Build a chronological series (oldest -> newest), skipping null/zero values. + final sorted = List.from(vitals); + sorted.sort((a, b) { + final ad = a.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + final bd = b.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + return ad.compareTo(bd); + }); + + const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + double index = 0; + for (final v in sorted) { + final diastolic = _toDouble(v.bloodPressureLower); + if (diastolic == null) continue; + if (diastolic == 0) continue; + + final dt = v.vitalSignDate ?? DateTime.now(); + final label = '${monthNames[dt.month - 1]}, ${dt.year}'; + + points.add( + DataPoint( + value: index, + label: label, + actualValue: diastolic.toStringAsFixed(0), + time: dt, + displayTime: '${dt.day}/${dt.month}/${dt.year}', + unitOfMeasurement: args.unit, + ), + ); + index += 1; + } + + return points; + } + double? _toDouble(dynamic v) { if (v == null) return null; if (v is num) return v.toDouble(); diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index ad47cd2..f114521 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -130,9 +130,23 @@ class CustomGraph extends StatelessWidget { minX: minX, lineTouchData: LineTouchData( getTouchLineEnd: (_, __) => 0, + handleBuiltInTouches: true, + touchCallback: (FlTouchEvent event, LineTouchResponse? touchResponse) { + // Let fl_chart handle the touch + }, getTouchedSpotIndicator: (barData, indicators) { - // Only show custom marker for touched spot + // Show custom marker for touched spot with correct color per line return indicators.map((int index) { + // Determine which line is being touched based on barData + Color dotColor = spotColor; + if (secondaryDataPoints != null && barData.spots.length > 0) { + // Check if this is the secondary line by comparing the first spot's color + final gradient = barData.gradient; + if (gradient != null && gradient.colors.isNotEmpty) { + dotColor = gradient.colors.first; + } + } + return TouchedSpotIndicatorData( FlLine(color: Colors.transparent), FlDotData( @@ -140,7 +154,7 @@ class CustomGraph extends StatelessWidget { getDotPainter: (spot, percent, barData, idx) { return FlDotCirclePainter( radius: 8, - color: spotColor, + color: dotColor, strokeWidth: 2, strokeColor: Colors.white, ); @@ -154,17 +168,18 @@ class CustomGraph extends StatelessWidget { getTooltipColor: (_) => Colors.white, getTooltipItems: (touchedSpots) { if (touchedSpots.isEmpty) return []; - // Only show tooltip for the first touched spot, hide others + // Show tooltip for each touched line return touchedSpots.map((spot) { - if (spot == touchedSpots.first) { - final dataPoint = dataPoints[spot.x.toInt()]; + // Determine which dataset this spot belongs to + final isSecondary = secondaryDataPoints != null && spot.barIndex == 1; + final dataPoint = isSecondary + ? secondaryDataPoints![spot.x.toInt()] + : dataPoints[spot.x.toInt()]; - return LineTooltipItem( - '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}', - TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500), - ); - } - return null; // hides the rest + return LineTooltipItem( + '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}', + TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500), + ); }).toList(); }, ), From 69666a6f6cdf18c7e1a5a24f76f565e7c3ad457f Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 13 Jan 2026 12:25:27 +0300 Subject: [PATCH 34/46] fixed button --- lib/core/dependencies.dart | 25 ++++----- lib/presentation/parking/paking_page.dart | 63 +++++++++++++--------- lib/presentation/parking/parking_slot.dart | 44 +++++++++------ pubspec.yaml | 2 +- 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index e8d3071..f6d5471 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -72,6 +72,7 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; @@ -290,11 +291,11 @@ class AppDependencies { getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); - getIt.registerLazySingleton(() => MyInvoicesViewModel( - myInvoicesRepo: getIt(), - errorHandlerService: getIt(), - navServices: getIt(), - )); + // getIt.registerLazySingleton(() => MyInvoicesViewModel( + // myInvoicesRepo: getIt(), + // errorHandlerService: getIt(), + // navServices: getIt(), + // )); getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton( () => ActivePrescriptionsViewModel( @@ -302,13 +303,13 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); + // getIt.registerFactory( + // () => QrParkingViewModel( + // qrParkingRepo: getIt(), + // errorHandlerService: getIt(), + // cacheService: getIt(), + // ), + // ); } } diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index cd1e8bc..f112d51 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/buttons/custom_button.dart'; import '../../widgets/routes/custom_page_route.dart'; @@ -110,32 +111,42 @@ class _ParkingPageState extends State { child: SizedBox( width: double.infinity, height: 56, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryRedColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: vm.isLoading ? null : () => _readQR(context), - child: vm.isLoading - ? const SizedBox( - width: 22, - height: 22, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Text( - "Read Barcodes", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), + child: CustomButton( + text: "Read Barcodes".needTranslation, + onPressed: () => _readQR(context), // ALWAYS non-null + isDisabled: vm.isLoading, // control disabled state here + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + fontSize: 18, + fontWeight: FontWeight.bold, + ) + + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: AppColors.primaryRedColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // ), + // onPressed: vm.isLoading ? null : () => _readQR(context), + // child: vm.isLoading + // ? const SizedBox( + // width: 22, + // height: 22, + // child: CircularProgressIndicator( + // strokeWidth: 2, + // color: Colors.white, + // ), + // ) + // : const Text( + // "Read Barcodes", + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: Colors.white, + // ), + // ), + // ), ), ), ), diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 013bb6f..ce13dad 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_respon import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; @@ -184,23 +185,34 @@ class _ParkingSlotState extends State { SizedBox( width: double.infinity, height: 48.h, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryRedColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), + child:CustomButton( + text: "Get Direction".needTranslation, onPressed: _openDirection, - child: Text( - "Get Direction".needTranslation, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.whiteColor, - ), - ), - ), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 18, + fontWeight: FontWeight.bold, + borderRadius: 10, + ) + + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: AppColors.primaryRedColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // ), + // onPressed: _openDirection, + // child: Text( + // "Get Direction".needTranslation, + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: AppColors.whiteColor, + // ), + // ), + // ), ), // const Spacer(), diff --git a/pubspec.yaml b/pubspec.yaml index 461d3ab..cdad394 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,7 +77,7 @@ dependencies: amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 flutter_nfc_kit: ^3.6.0 - barcode_scan2: ^4.5.1 + barcode_scan2: ^4.6.0 keyboard_actions: ^4.2.0 path_provider: ^2.0.8 open_filex: ^4.7.0 From a0008c91046fed88dea1e7b9862edee52c7f82cc Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 13 Jan 2026 13:13:21 +0300 Subject: [PATCH 35/46] Nearest Appointment sorting fixed --- lib/core/dependencies.dart | 2 +- .../book_appointments_view_model.dart | 19 ++++++- .../hmg_services/hmg_services_view_model.dart | 4 ++ .../search_doctor_by_name.dart | 1 - .../book_appointment/select_doctor_page.dart | 3 +- .../book_appointment/widgets/doctor_card.dart | 55 +++++++++++++------ .../profile_settings/profile_settings.dart | 12 ++-- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 6e203da..6c7d358 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -292,7 +292,7 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); //commenting this because its already define there was on run time error because of this. - // getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index a225c1d..3f5517a 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -289,9 +289,24 @@ class BookAppointmentsViewModel extends ChangeNotifier { this.isNearestAppointmentSelected = isNearestAppointmentSelected; if (isNearestAppointmentSelected) { - doctorsList.sort((a, b) => DateUtil.convertStringToDate(a.nearestFreeSlot!).compareTo(DateUtil.convertStringToDate(b.nearestFreeSlot!))); + for (var group in doctorsListGrouped) { + group.sort((a, b) { + var aSlot = a.nearestFreeSlot; + var bSlot = b.nearestFreeSlot; + if (aSlot == null || bSlot == null) return 0; + return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot)); + }); + } } else { - doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); + for (var group in doctorsListGrouped) { + group.sort((a, b) { + var aSlot = a.decimalDoctorRate; + var bSlot = b.decimalDoctorRate; + if (aSlot == null || bSlot == null) return 0; + return bSlot.compareTo(aSlot); + }); + } + // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); } notifyListeners(); diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index c55a11c..daddbff 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -40,6 +40,8 @@ class HmgServicesViewModel extends ChangeNotifier { bool isHospitalListLoading = false; bool isVitalSignLoading = false; + bool hasVitalSignDataLoaded = false; + // HHC specific loading states bool isHhcOrdersLoading = false; bool isHhcServicesLoading = false; @@ -878,6 +880,7 @@ class HmgServicesViewModel extends ChangeNotifier { Function(dynamic)? onSuccess, Function(String)? onError, }) async { + if (hasVitalSignDataLoaded) return; isVitalSignLoading = true; notifyListeners(); @@ -896,6 +899,7 @@ class HmgServicesViewModel extends ChangeNotifier { isVitalSignLoading = false; if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; + hasVitalSignDataLoaded = true; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index eb6d06f..9f2da16 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -262,7 +262,6 @@ class _SearchDoctorByNameState extends State { final doctorIndex = entry.key; final doctor = entry.value; final isLastDoctor = doctorIndex == groupedDoctors[index].length - 1; - return Column( children: [ DoctorCard( diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 28b0f61..2f8747d 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -198,7 +198,8 @@ class _SelectDoctorPageState extends State { physics: NeverScrollableScrollPhysics(), itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.doctorsListGrouped.isNotEmpty ? bookAppointmentsVM.doctorsListGrouped.length : 1), itemBuilder: (context, index) { - final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + // final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + final isExpanded = true; return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( doctorsListResponseModel: DoctorsListResponseModel(), diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 278c100..d2e1c0b 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -43,15 +43,44 @@ class DoctorCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - isLoading - ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" - : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", - width: 63.h, - height: 63.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: isLoading), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + isLoading + ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" + : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: isLoading), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, // Makes the container circular + border: Border.all( + color: AppColors.scaffoldBgColor, // Color of the border + width: 1.5.w, // Width of the border + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), + SizedBox(height: 2.h), + "${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100).toShimmer2(isShow: isLoading), + ), + ], + ), SizedBox(width: 8.h), Expanded( flex: 9, @@ -102,23 +131,17 @@ class DoctorCard extends StatelessWidget { ), ], ), - SizedBox(height: 12.h), Wrap( direction: Axis.horizontal, spacing: 3.h, runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "Clinic: ${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, + labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: "Branch: ${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, + labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, ).toShimmer2(isShow: isLoading), - doctorsListResponseModel.decimalDoctorRate != null ? AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".needTranslation, - ).toShimmer2(isShow: isLoading) : SizedBox(), bookAppointmentsViewModel.isNearestAppointmentSelected ? doctorsListResponseModel.nearestFreeSlot != null ? AppCustomChipWidget( diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index e463ae7..62f090e 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -217,12 +217,12 @@ class ProfileSettingsState extends State { child: Column( children: [ actionItem(AppAssets.email_transparent, "Update Email Address".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), ], ), ), From 8f428297cbd1eda5228c9cc5f02b10731fa82497 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 13 Jan 2026 16:21:13 +0300 Subject: [PATCH 36/46] Arabic translations added --- assets/langs/ar-SA.json | 134 ++++++++++++++++- assets/langs/en-US.json | 136 +++++++++++++++++- lib/generated/locale_keys.g.dart | 132 ++++++++++++++++- .../appointment_details_page.dart | 63 ++++---- .../appointment_payment_page.dart | 23 ++- .../appointments/appointment_queue_page.dart | 50 +++---- .../appointments/my_appointments_page.dart | 12 +- .../appointments/my_doctors_page.dart | 4 +- .../widgets/appointment_card.dart | 14 +- .../appointment_checkin_bottom_sheet.dart | 26 ++-- .../widgets/appointment_doctor_card.dart | 4 +- .../ask_doctor_request_type_select.dart | 2 +- .../facility_type_selection_widget.dart | 4 +- .../hospital_list_items.dart | 6 +- .../type_selection_widget.dart | 6 +- lib/presentation/authentication/login.dart | 2 +- lib/presentation/authentication/register.dart | 2 +- .../book_appointment_page.dart | 52 +++---- .../dental_chief_complaints_page.dart | 4 +- .../doctor_filter/doctors_filter.dart | 2 +- .../book_appointment/doctor_profile_page.dart | 14 +- .../laser/laser_appointment.dart | 4 +- .../immediate_livecare_payment_details.dart | 40 +++--- .../immediate_livecare_payment_page.dart | 30 ++-- ...mediate_livecare_pending_request_page.dart | 8 +- ...select_immediate_livecare_clinic_page.dart | 10 +- .../widgets/livecare_clinic_card.dart | 2 +- .../widgets/select_livecare_call_type.dart | 14 +- .../review_appointment_page.dart | 16 +-- .../search_doctor_by_name.dart | 4 +- .../book_appointment/select_clinic_page.dart | 16 +-- .../book_appointment/select_doctor_page.dart | 8 +- .../select_livecare_clinic_page.dart | 20 +-- .../waiting_appointment_info.dart | 16 +-- ...ting_appointment_online_checkin_sheet.dart | 27 ++-- .../waiting_appointment_payment_page.dart | 36 +++-- .../widgets/appointment_calendar.dart | 8 +- .../book_appointment/widgets/doctor_card.dart | 10 +- .../cmc_order_detail_page.dart | 9 +- .../cmc_hospital_bottom_sheet_body.dart | 4 +- .../widgets/cmc_hospital_list_item.dart | 4 +- .../widgets/cmc_ui_selection_helper.dart | 6 +- 42 files changed, 676 insertions(+), 308 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index b8fc7eb..ee6ac33 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -877,5 +877,137 @@ "walkin": "زيارة بدون موعد", "laserClinic": "عيادة الليزر", "continueString": "يكمل", - "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية" + "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية", + + "appointmentDetails": "تفاصيل الموعد", + "checkingDoctorAvailability": "جاري التحقق من توفر الطبيب...", + "cancellingAppointmentPleaseWait": "جاري إلغاء الموعد، يرجى الانتظار...", + "appointmentCancelledSuccessfully": "تم إلغاء الموعد بنجاح", + "notConfirmed": "غير مؤكد", + "appointmentStatus": "حالة الموعد", + "doctorWillCallYou": "سيتصل بك الطبيب عندما يقترب موعدك.", + "getDirections": "الحصول على الاتجاهات", + "notifyMeBeforeAppointment": "أبلغني قبل الموعد", + "fetchingLabResults": "جاري جلب نتائج المختبر...", + "fetchingRadiologyResults": "جاري جلب نتائج الأشعة...", + "fetchingAppointmentPrescriptions": "جاري جلب وصفات الموعد...", + "noPrescriptionsForAppointment": "ليس لديك أي وصفات طبية لهذا الموعد.", + "amountBeforeTax": "المبلغ قبل الضريبة", + "rebookAppointment": "إعادة حجز الموعد", + "fetchingDoctorSchedulePleaseWait": "جاري جلب جدول الطبيب، يرجى الانتظار...", + "pickADate": "اختر تاريخاً", + "confirmingAppointmentPleaseWait": "جاري تأكيد الموعد، يرجى الانتظار...", + "appointmentConfirmedSuccessfully": "تم تأكيد الموعد بنجاح", + "appointmentPayment": "دفع الموعد", + "checkingPaymentStatusPleaseWait": "جاري التحقق من حالة الدفع، يرجى الانتظار...", + "paymentFailedPleaseTryAgain": "فشل الدفع! يرجى المحاولة مرة أخرى.", + "appointmentCheckIn": "تسجيل حضور الموعد", + "insuranceExpiredOrInactive": "التأمين منتهي الصلاحية أو غير نشط", + "totalAmountToPay": "المبلغ الإجمالي المستحق", + "vat15": "ضريبة القيمة المضافة 15%", + "general": "عام", + "liveCare": "لايف كير", + "recentVisits": "الزيارات الأخيرة", + "searchByClinic": "البحث حسب العيادة", + "tapToSelectClinic": "انقر لاختيار العيادة", + "searchByDoctor": "البحث حسب الطبيب", + "tapToSelect": "انقر للاختيار", + "searchByRegion": "البحث حسب المنطقة", + "centralRegion": "المنطقة الوسطى", + "immediateConsultation": "استشارة فورية", + "scheduledConsultation": "استشارة مجدولة", + "pharmaLiveCare": "لايف كير الصيدلية", + "notSureHelpMeChooseClinic": "غير متأكد؟ ساعدني في اختيار عيادة!", + "mentionYourSymptomsAndFindDoctors": "اذكر أعراضك واعثر على قائمة الأطباء وفقاً لذلك", + "immediateService": "خدمة فورية", + "noNeedToWaitGetMedicalConsultation": "لا حاجة للانتظار، ستحصل على استشارة طبية فورية عبر مكالمة فيديو", + "noVisitRequired": "لا حاجة للزيارة", + "doctorWillContact": "سيتصل بك الطبيب", + "specialisedDoctorWillContactYou": "سيتصل بك طبيب متخصص وسيكون قادراً على الاطلاع على تاريخك الطبي", + "freeMedicineDelivery": "توصيل مجاني للأدوية", + "offersFreeMedicineDelivery": "يوفر توصيل مجاني للأدوية لموعد لايف كير", + "dentalChiefComplaints": "الشكاوى الرئيسة للأسنان", + "viewAvailableAppointments": "عرض المواعيد المتاحة", + "doctorProfile": "الملف للطبيب", + "waitingAppointment": "موعد الانتظار", + "hospitalInformation": "معلومات المستشفى", + "fetchingAppointmentShare": "جاري جلب تفاصيل الموعد...", + "bookingYourAppointment": "جاري حجز موعدك...", + "selectLiveCareClinic": "اختر عيادة لايف كير", + "checkingForExistingDentalPlan": "جاري التحقق من وجود خطة أسنان حالية، يرجى الانتظار...", + "dentalTreatmentPlan": "خطة علاج الأسنان", + "youHaveExistingTreatmentPlan": "لديك خطة علاج حالية: ", + "mins": "دقيقة", + "totalTimeRequired": "إجمالي الوقت المطلوب", + "wouldYouLikeToContinue": "هل تريد متابعتها؟", + "chooseDoctor": "اختر الطبيب", + "viewNearestAppos": "عرض أقرب المواعيد المتاحة", + "noDoctorFound": "لم يتم العثور على طبيب مطابق للمعايير المحددة...", + "yesPleasImInAHurry": "نعم من فضلك، أنا في عجلة من أمري", + "fetchingFeesPleaseWait": "جاري جلب الرسوم، يرجى الانتظار...", + "noThanksPhysicalVisit": "لا، شكراً. أفضل الزيارة الشخصية", + "offline": "غير متصل", + "videoCall": "مكالمة فيديو", + "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع أطباء مجموعة الحبيب الطبية", + "audioCall": "مكالمة صوتية", + "phoneCall": "مكالمة هاتفية", + "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع أطباء مجموعة الحبيب الطبية", + "reviewLiveCareRequest": "مراجعة طلب لايف كير", + "selectedLiveCareType": "نوع لايف كير المحدد", + "selectLiveCareCallType": "اختر نوع مكالمة لايف كير", + "confirmingLiveCareRequest": "جاري تأكيد طلب لايف كير، يرجى الانتظار...", + "unknownErrorOccurred": "حدث خطأ غير معروف...", + "liveCarePermissionsMessage": "يتطلب لايف كير أذونات الكاميرا والميكروفون والموقع والإشعارات لتمكين الاستشارة الافتراضية بين المريض والطبيب، يرجى السماح بهذه الأذونات للمتابعة.", + "liveCarePayment": "دفع لايف كير", + "mada": "مدى", + "visaOrMastercard": "فيزا أو ماستركارد", + "tamara": "تمارا", + "fetchingApplePayDetails": "جاري جلب تفاصيل Apple Pay، يرجى الانتظار...", + "liveCarePendingRequest": "لايف كير حية معلق", + "callLiveCareSupport": "اتصل بدعم لايف كير", + "whatIsWaitingAppointment": "ما هو موعد الانتظار؟", + "waitingAppointmentsFeature": "تتيح لك ميزة مواعيد الانتظار حجز موعد أثناء تواجدك داخل مبنى المستشفى، وفي حال عدم توفر فتحة متاحة في جدول الطبيب.", + "appointmentWithDoctorConfirmed": "الموعد مع الطبيب مؤكد، ولكن وقت الدخول غير محدد.", + "paymentWithinTenMinutes": "ملاحظة: يجب عليك الدفع خلال 10 دقائق من الحجز، وإلا سيتم إلغاء موعدك تلقائياً", + "liveLocation": "الموقع المباشر", + "verifyYourLocationAtHospital": "تحقق من موقعك في المستشفى لتسجيل الحضور", + "error": "خطأ", + "ensureWithinHospitalLocation": "يرجى التأكد من أنك داخل موقع المستشفى لإجراء تسجيل الحضور عبر الإنترنت.", + "nfcNearFieldCommunication": "NFC (الاتصال قريب المدى)", + "scanPhoneViaNFC": "امسح هاتفك عبر لوحة NFC لتسجيل الحضور", + "qrCode": "رمز QR", + "scanQRCodeToCheckIn": "امسح رمز QR بالكاميرا لتسجيل الحضور", + "processingCheckIn": "جاري معالجة تسجيل الحضور...", + "bookingWaitingAppointment": "جاري حجز موعد الانتظار، يرجى الانتظار...", + "enterValidIDorIqama": "يرجى إدخال رقم هوية وطنية أو رقم ملف صالح", + "selectAppointment": "حدد الموعد", + "rebookSameDoctor": "أعد الحجز مع نفس الطبيب", + "queueing": "قائمة الانتظار", + "inQueue": "في قائمة الانتظار", + "yourTurn": "دورك", + "halaFirstName": "هلا {firstName}!!!", + "thankYouForPatience": "شكراً لصبرك، هذا هو رقم قائمة الانتظار الخاص بك.", + "servingNow": "يُخدم الآن", + "callForVitalSigns": "نداء للعلامات الحيوية", + "callForDoctor": "نداء للطبيب", + "thingsToAskDoctor": "أشياء تسأل طبيبك عنها اليوم", + "improveOverallHealth": "ماذا يمكنني أن أفعل لتحسين صحتي العامة؟", + "routineScreenings": "هل هناك أي فحوصات روتينية يجب أن أجريها؟", + "whatIsThisMedicationFor": "لماذا هذا الدواء؟", + "sideEffectsToKnow": "هل هناك أي آثار جانبية يجب أن أعرفها؟", + "whenFollowUp": "متى يجب أن أعود للمتابعة؟", + "goToHomepage": "الذهاب إلى الصفحة الرئيسية", + "appointmentsList": "قائمة المواعيد", + "allAppt": "جميع المواعيد", + "upcoming": "القادمة", + "completed": "المكتملة", + "noAppointmentsYet": "ليس لديك أي مواعيد بعد.", + "viewProfile": "عرض الملف الشخصي", + "choosePreferredHospitalForService": "اختر المستشفى المفضل لديك للخدمة", + "noHospitalsFound": "لم يتم العثور على مستشفيات", + "cancelOrderConfirmation": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟", + "orderCancelledSuccessfully": "تم إلغاء الطلب بنجاح", + "requestID": "معرف الطلب:", + "noCMCOrdersYet": "ليس لديك أي طلبات فحص شامل بعد.", + "cmcOrders": "طلبات الفحص الشامل" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 7839083..6244157 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -864,7 +864,7 @@ "endDate": "End Date", "hmgHospitals": "HMG Hospitals", "hmcMedicalClinic": "HMC Medical Centers", - "applyFilter": "AppLy Filter", + "applyFilter": "Apply Filter", "facilityAndLocation": "Facility and Location", "regionAndLocation": "Region And Locations", "clearAllFilters": "Clear all filters", @@ -873,5 +873,137 @@ "walkin": "Walk In", "continueString": "Continue", "laserClinic": "Laser Clinic", - "covid_info" :"Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention." + "covid_info" :"Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", + "appointmentDetails": "Appointment Details", + "checkingDoctorAvailability": "Checking doctor availability...", + "cancellingAppointmentPleaseWait": "Cancelling Appointment, Please Wait...", + "appointmentCancelledSuccessfully": "Appointment Cancelled Successfully", + "notConfirmed": "Not Confirmed", + "appointmentStatus": "Appointment Status", + "doctorWillCallYou": "The doctor will call you once the appointment time approaches.", + "getDirections": "Get Directions", + "notifyMeBeforeAppointment": "Notify me before the appointment", + "fetchingLabResults": "Fetching Lab Results...", + "fetchingRadiologyResults": "Fetching Radiology Results...", + "fetchingAppointmentPrescriptions": "Fetching Appointment Prescriptions...", + "noPrescriptionsForAppointment": "You don't have any prescriptions for this appointment.", + "amountBeforeTax": "Amount before tax", + "rebookAppointment": "Re-book Appointment", + "fetchingDoctorSchedulePleaseWait": "Fetching Doctor Schedule, Please Wait...", + "pickADate": "Pick a Date", + "confirmingAppointmentPleaseWait": "Confirming Appointment, Please Wait...", + "appointmentConfirmedSuccessfully": "Appointment Confirmed Successfully", + + "appointmentPayment": "Appointment Payment", + "checkingPaymentStatusPleaseWait": "Checking payment status, Please wait...", + "paymentFailedPleaseTryAgain": "Payment Failed! Please try again.", + "appointmentCheckIn": "Appointment check in", + "insuranceExpiredOrInactive": "Insurance expired or inactive", + "totalAmountToPay": "Total amount to pay", + "vat15": "VAT 15%", + "general": "General", + "liveCare": "LiveCare", + "recentVisits": "Recent Visits", + "searchByClinic": "Search By Clinic", + "tapToSelectClinic": "Tap to select clinic", + "searchByDoctor": "Search By Doctor", + "tapToSelect": "Tap to select", + "searchByRegion": "Search By Region", + "centralRegion": "Central Region", + "immediateConsultation": "Immediate Consultation", + "scheduledConsultation": "Scheduled Consultation", + "pharmaLiveCare": "Pharma LiveCare", + "notSureHelpMeChooseClinic": "Not sure? help me choose a clinic!", + "mentionYourSymptomsAndFindDoctors": "Mention your symptoms and find the list of doctors accordingly", + "immediateService": "Immediate service", + "noNeedToWaitGetMedicalConsultation": "No need to wait, you will get medical consultation immediately via video call", + "noVisitRequired": "No visit required", + "doctorWillContact": "Doctor will contact", + "specialisedDoctorWillContactYou": "A specialised doctor will contact you and will be able to view your medical history", + "freeMedicineDelivery": "Free medicine delivery", + "offersFreeMedicineDelivery": "Offers free medicine delivery for the LiveCare appointment", + "dentalChiefComplaints": "Dental Chief Complaints", + "viewAvailableAppointments": "View available appointments", + "doctorProfile": "Doctor Profile", + "waitingAppointment": "Waiting Appointment", + "hospitalInformation": "Hospital Information", + "fetchingAppointmentShare": "Fetching Appointment Share...", + "bookingYourAppointment": "Booking your appointment...", + "selectLiveCareClinic": "Select LiveCare Clinic", + "checkingForExistingDentalPlan": "Checking for an existing dental plan, Please wait...", + "dentalTreatmentPlan": "Dental treatment plan", + "youHaveExistingTreatmentPlan": "You have an existing treatment plan: ", + "mins": "Mins", + "totalTimeRequired": "Total time required", + "wouldYouLikeToContinue": "Would you like to continue it?", + "chooseDoctor": "Choose Doctor", + "viewNearestAppos": "View nearest available appointments", + "noDoctorFound": "No Doctor found for selected criteria...", + "yesPleasImInAHurry": "Yes please, I am in a hurry", + "fetchingFeesPleaseWait": "Fetching fees, Please wait...", + "noThanksPhysicalVisit": "No, Thanks. I would like a physical visit", + "offline": "Offline", + "videoCall": "Video Call", + "liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors", + "audioCall": "Audio Call", + "phoneCall": "Phone Call", + "livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors", + "reviewLiveCareRequest": "Review LiveCare Request", + "selectedLiveCareType": "Selected LiveCare Type", + "selectLiveCareCallType": "Select LiveCare call type", + "confirmingLiveCareRequest": "Confirming LiveCare request, Please wait...", + "unknownErrorOccurred": "Unknown error occurred...", + "liveCarePermissionsMessage": "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.", + "liveCarePayment": "LiveCare Payment", + "mada": "Mada", + "visaOrMastercard": "Visa or Mastercard", + "tamara": "Tamara", + "fetchingApplePayDetails": "Fetching Apple Pay details, Please wait...", + "liveCarePendingRequest": "LiveCare Pending Request", + "callLiveCareSupport": "Call LiveCare Support", + "whatIsWaitingAppointment": "What is Waiting Appointment?", + "waitingAppointmentsFeature": "The waiting appointments feature allows you to book an appointment while you are inside the hospital building, and in case there is no available slot in the doctor's schedule.", + "appointmentWithDoctorConfirmed": "The appointment with the doctor is confirmed, but the time of entry is uncertain.", + "paymentWithinTenMinutes": "Note: You must have to pay within 10 minutes of booking, otherwise your appointment will be cancelled automatically", + "liveLocation": "Live Location", + "verifyYourLocationAtHospital": "Verify your location to be at hospital to check in", + "error": "Error", + "ensureWithinHospitalLocation": "Please ensure you're within the hospital location to perform online check-in.", + "nfcNearFieldCommunication": "NFC (Near Field Communication)", + "scanPhoneViaNFC": "Scan your phone via NFC board to check in", + "qrCode": "QR Code", + "scanQRCodeToCheckIn": "Scan QR code with your camera to check in", + "processingCheckIn": "Processing Check-In...", + "bookingWaitingAppointment": "Booking Waiting Appointment, Please wait...", + "enterValidIDorIqama": "Please enter a valid national ID or file number", + "selectAppointment": "Select Appointment", + "rebookSameDoctor": "Rebook with same doctor", + "queueing": "Queueing", + "inQueue": "In Queue", + "yourTurn": "Your Turn", + "halaFirstName": "Hala {firstName}!!!", + "thankYouForPatience": "Thank you for your patience, here is your queue number.", + "servingNow": "Serving Now", + "callForVitalSigns": "Call for vital signs", + "callForDoctor": "Call for Doctor", + "thingsToAskDoctor": "Things to ask your doctor today", + "improveOverallHealth": "What can I do to improve my overall health?", + "routineScreenings": "Are there any routine screenings I should get?", + "whatIsThisMedicationFor": "What is this medication for?", + "sideEffectsToKnow": "Are there any side effects I should know about?", + "whenFollowUp": "When should I come back for a follow-up?", + "goToHomepage": "Go to homepage", + "appointmentsList": "Appointments List", + "allAppt": "All Appt.", + "upcoming": "Upcoming", + "completed": "Completed", + "noAppointmentsYet": "You don't have any appointments yet.", + "viewProfile": "View Profile", + "choosePreferredHospitalForService": "Choose your preferred hospital for the service", + "noHospitalsFound": "No hospitals Found", + "cancelOrderConfirmation": "Are you sure you want to cancel this order?", + "orderCancelledSuccessfully": "Order has been cancelled successfully", + "requestID": "Request ID:", + "noCMCOrdersYet": "You don't have any CMC orders yet.", + "cmcOrders": "CMC Orders" } \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f550c81..f57a48b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -475,7 +475,7 @@ abstract class LocaleKeys { static const shareReview = 'shareReview'; static const review = 'review'; static const viewMedicalFile = 'viewMedicalFile'; - static String get viewAllServices => 'viewAllServices'; + static const viewAllServices = 'viewAllServices'; static const medicalFile = 'medicalFile'; static const verified = 'verified'; static const checkup = 'checkup'; @@ -876,5 +876,135 @@ abstract class LocaleKeys { static const laserClinic = 'laserClinic'; static const continueString = 'continueString'; static const covid_info = 'covid_info'; + static const appointmentDetails = 'appointmentDetails'; + static const checkingDoctorAvailability = 'checkingDoctorAvailability'; + static const cancellingAppointmentPleaseWait = 'cancellingAppointmentPleaseWait'; + static const appointmentCancelledSuccessfully = 'appointmentCancelledSuccessfully'; + static const notConfirmed = 'notConfirmed'; + static const appointmentStatus = 'appointmentStatus'; + static const doctorWillCallYou = 'doctorWillCallYou'; + static const getDirections = 'getDirections'; + static const notifyMeBeforeAppointment = 'notifyMeBeforeAppointment'; + static const fetchingLabResults = 'fetchingLabResults'; + static const fetchingRadiologyResults = 'fetchingRadiologyResults'; + static const fetchingAppointmentPrescriptions = 'fetchingAppointmentPrescriptions'; + static const noPrescriptionsForAppointment = 'noPrescriptionsForAppointment'; + static const amountBeforeTax = 'amountBeforeTax'; + static const rebookAppointment = 'rebookAppointment'; + static const fetchingDoctorSchedulePleaseWait = 'fetchingDoctorSchedulePleaseWait'; + static const pickADate = 'pickADate'; + static const confirmingAppointmentPleaseWait = 'confirmingAppointmentPleaseWait'; + static const appointmentConfirmedSuccessfully = 'appointmentConfirmedSuccessfully'; + static const appointmentPayment = 'appointmentPayment'; + static const checkingPaymentStatusPleaseWait = 'checkingPaymentStatusPleaseWait'; + static const paymentFailedPleaseTryAgain = 'paymentFailedPleaseTryAgain'; + static const appointmentCheckIn = 'appointmentCheckIn'; + static const insuranceExpiredOrInactive = 'insuranceExpiredOrInactive'; + static const totalAmountToPay = 'totalAmountToPay'; + static const vat15 = 'vat15'; + static const liveCare = 'liveCare'; + static const recentVisits = 'recentVisits'; + static const searchByClinic = 'searchByClinic'; + static const tapToSelectClinic = 'tapToSelectClinic'; + static const searchByDoctor = 'searchByDoctor'; + static const tapToSelect = 'tapToSelect'; + static const searchByRegion = 'searchByRegion'; + static const centralRegion = 'centralRegion'; + static const immediateConsultation = 'immediateConsultation'; + static const scheduledConsultation = 'scheduledConsultation'; + static const pharmaLiveCare = 'pharmaLiveCare'; + static const notSureHelpMeChooseClinic = 'notSureHelpMeChooseClinic'; + static const mentionYourSymptomsAndFindDoctors = 'mentionYourSymptomsAndFindDoctors'; + static const immediateService = 'immediateService'; + static const noNeedToWaitGetMedicalConsultation = 'noNeedToWaitGetMedicalConsultation'; + static const noVisitRequired = 'noVisitRequired'; + static const doctorWillContact = 'doctorWillContact'; + static const specialisedDoctorWillContactYou = 'specialisedDoctorWillContactYou'; + static const freeMedicineDelivery = 'freeMedicineDelivery'; + static const offersFreeMedicineDelivery = 'offersFreeMedicineDelivery'; + static const dentalChiefComplaints = 'dentalChiefComplaints'; + static const viewAvailableAppointments = 'viewAvailableAppointments'; + static const doctorProfile = 'doctorProfile'; + static const waitingAppointment = 'waitingAppointment'; + static const hospitalInformation = 'hospitalInformation'; + static const fetchingAppointmentShare = 'fetchingAppointmentShare'; + static const bookingYourAppointment = 'bookingYourAppointment'; + static const selectLiveCareClinic = 'selectLiveCareClinic'; + static const checkingForExistingDentalPlan = 'checkingForExistingDentalPlan'; + static const dentalTreatmentPlan = 'dentalTreatmentPlan'; + static const youHaveExistingTreatmentPlan = 'youHaveExistingTreatmentPlan'; + static const mins = 'mins'; + static const totalTimeRequired = 'totalTimeRequired'; + static const wouldYouLikeToContinue = 'wouldYouLikeToContinue'; + static const chooseDoctor = 'chooseDoctor'; + static const viewNearestAppos = 'viewNearestAppos'; + static const noDoctorFound = 'noDoctorFound'; + static const yesPleasImInAHurry = 'yesPleasImInAHurry'; + static const fetchingFeesPleaseWait = 'fetchingFeesPleaseWait'; + static const noThanksPhysicalVisit = 'noThanksPhysicalVisit'; + static const offline = 'offline'; + static const videoCall = 'videoCall'; + static const liveVideoCallWithHMGDoctors = 'liveVideoCallWithHMGDoctors'; + static const audioCall = 'audioCall'; + static const phoneCall = 'phoneCall'; + static const livePhoneCallWithHMGDoctors = 'livePhoneCallWithHMGDoctors'; + static const reviewLiveCareRequest = 'reviewLiveCareRequest'; + static const selectedLiveCareType = 'selectedLiveCareType'; + static const selectLiveCareCallType = 'selectLiveCareCallType'; + static const confirmingLiveCareRequest = 'confirmingLiveCareRequest'; + static const unknownErrorOccurred = 'unknownErrorOccurred'; + static const liveCarePermissionsMessage = 'liveCarePermissionsMessage'; + static const liveCarePayment = 'liveCarePayment'; + static const mada = 'mada'; + static const visaOrMastercard = 'visaOrMastercard'; + static const tamara = 'tamara'; + static const fetchingApplePayDetails = 'fetchingApplePayDetails'; + static const liveCarePendingRequest = 'liveCarePendingRequest'; + static const callLiveCareSupport = 'callLiveCareSupport'; + static const whatIsWaitingAppointment = 'whatIsWaitingAppointment'; + static const waitingAppointmentsFeature = 'waitingAppointmentsFeature'; + static const appointmentWithDoctorConfirmed = 'appointmentWithDoctorConfirmed'; + static const paymentWithinTenMinutes = 'paymentWithinTenMinutes'; + static const liveLocation = 'liveLocation'; + static const verifyYourLocationAtHospital = 'verifyYourLocationAtHospital'; + static const error = 'error'; + static const ensureWithinHospitalLocation = 'ensureWithinHospitalLocation'; + static const nfcNearFieldCommunication = 'nfcNearFieldCommunication'; + static const scanPhoneViaNFC = 'scanPhoneViaNFC'; + static const qrCode = 'qrCode'; + static const scanQRCodeToCheckIn = 'scanQRCodeToCheckIn'; + static const processingCheckIn = 'processingCheckIn'; + static const bookingWaitingAppointment = 'bookingWaitingAppointment'; + static const enterValidIDorIqama = 'enterValidIDorIqama'; + static const selectAppointment = 'selectAppointment'; + static const rebookSameDoctor = 'rebookSameDoctor'; + static const queueing = 'queueing'; + static const inQueue = 'inQueue'; + static const yourTurn = 'yourTurn'; + static const halaFirstName = 'halaFirstName'; + static const thankYouForPatience = 'thankYouForPatience'; + static const servingNow = 'servingNow'; + static const callForVitalSigns = 'callForVitalSigns'; + static const callForDoctor = 'callForDoctor'; + static const thingsToAskDoctor = 'thingsToAskDoctor'; + static const improveOverallHealth = 'improveOverallHealth'; + static const routineScreenings = 'routineScreenings'; + static const whatIsThisMedicationFor = 'whatIsThisMedicationFor'; + static const sideEffectsToKnow = 'sideEffectsToKnow'; + static const whenFollowUp = 'whenFollowUp'; + static const goToHomepage = 'goToHomepage'; + static const appointmentsList = 'appointmentsList'; + static const allAppt = 'allAppt'; + static const upcoming = 'upcoming'; + static const completed = 'completed'; + static const noAppointmentsYet = 'noAppointmentsYet'; + static const viewProfile = 'viewProfile'; + static const choosePreferredHospitalForService = 'choosePreferredHospitalForService'; + static const noHospitalsFound = 'noHospitalsFound'; + static const cancelOrderConfirmation = 'cancelOrderConfirmation'; + static const orderCancelledSuccessfully = 'orderCancelledSuccessfully'; + static const requestID = 'requestID'; + static const noCMCOrdersYet = 'noCMCOrdersYet'; + static const cmcOrders = 'cmcOrders'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index bd81584..0b8066e 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -87,7 +87,7 @@ class _AppointmentDetailsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Appointment Details".needTranslation, + title: LocaleKeys.appointmentDetails.tr(), report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () { contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); @@ -105,14 +105,13 @@ class _AppointmentDetailsPageState extends State { AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr()); await myAppointmentsViewModel.isDoctorAvailable( projectID: widget.patientAppointmentHistoryResponseModel.projectID, doctorId: widget.patientAppointmentHistoryResponseModel.doctorID, clinicId: widget.patientAppointmentHistoryResponseModel.clinicID, onSuccess: (value) async { if (value) { - print("Doctor is available"); await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( @@ -129,14 +128,14 @@ class _AppointmentDetailsPageState extends State { ); }); } else { - print("Doctor is not available"); + debugPrint("Doctor is not available"); } }); }, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - LoaderBottomSheet.showLoader(loadingText: "Cancelling Appointment, Please Wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { @@ -145,7 +144,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentCancelledSuccessfully.tr()), callBackFunc: () { Navigator.of(context).pop(); }, @@ -182,13 +181,13 @@ class _AppointmentDetailsPageState extends State { children: [ Row( children: [ - "Appointment Status".needTranslation.toText16(isBold: true), + LocaleKeys.appointmentStatus.tr().toText16(isBold: true), ], ), SizedBox(height: 4.h), (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + ? LocaleKeys.notConfirmed.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : LocaleKeys.confirmed.tr().toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), SizedBox(height: 16.h), //TODO Add countdown timer in case of LiveCare Appointment widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false @@ -200,9 +199,7 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "The doctor will call you once the appointment time approaches." - .needTranslation - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillCallYou.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -224,11 +221,11 @@ class _AppointmentDetailsPageState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - text: "Get Directions".needTranslation, onPressed: () { MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); }, + text: LocaleKeys.getDirections.tr(), backgroundColor: AppColors.textColor.withValues(alpha: 0.8), borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), textColor: AppColors.whiteColor, @@ -283,9 +280,7 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment" - .needTranslation - .toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.notifyMeBeforeAppointment.tr().toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), const Spacer(), @@ -307,8 +302,9 @@ class _AppointmentDetailsPageState extends State { "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", "", "", - title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}".needTranslation, - description:"${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}".needTranslation, + title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + description: + "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", onSuccess: () { setState(() { myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); @@ -318,10 +314,10 @@ class _AppointmentDetailsPageState extends State { onMultiDateSuccess: (int selectedIndex) async { isEventAddedOrRemoved = await calender.createOrUpdateEvent( - title: "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget - .patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}".needTranslation, - description: "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget - .patientAppointmentHistoryResponseModel.projectName}", + title: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + description: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", scheduleDateTime: DateUtil.convertStringToDate(widget .patientAppointmentHistoryResponseModel.appointmentDate), eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", @@ -369,7 +365,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Lab Results...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr()); await labViewModel.getLabResultsByAppointmentNo( appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, projectID: widget.patientAppointmentHistoryResponseModel.projectID, @@ -402,7 +398,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Radiology Results...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr()); await radiologyViewModel.getPatientRadiologyOrdersByAppointment( appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, projectID: widget.patientAppointmentHistoryResponseModel.projectID, @@ -429,7 +425,7 @@ class _AppointmentDetailsPageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment Prescriptions...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr()); await prescriptionsViewModel.getPrescriptionDetails( getPrescriptionRequestModel(), onSuccess: (val) { @@ -457,8 +453,7 @@ class _AppointmentDetailsPageState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget( - loadingText: "You don't have any prescriptions for this appointment.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -713,7 +708,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText18(isBold: true), + LocaleKeys.amountBeforeTax.tr().toText18(isBold: true), Utils.getPaymentAmountWithSymbol( widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, @@ -730,7 +725,6 @@ class _AppointmentDetailsPageState extends State { .tr(context: context) .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})" - .needTranslation .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -2), ], ), @@ -758,7 +752,7 @@ class _AppointmentDetailsPageState extends State { ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? CustomButton( - text: "Re-book Appointment".needTranslation, + text: LocaleKeys.rebookAppointment.tr(), onPressed: () { openDoctorScheduleCalendar(); }, @@ -816,13 +810,13 @@ class _AppointmentDetailsPageState extends State { projectName: widget.patientAppointmentHistoryResponseModel.projectName, ); bookAppointmentsViewModel.setSelectedDoctor(doctor); - LoaderBottomSheet.showLoader(loadingText: "Fetching Doctor Schedule, Please Wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingDoctorSchedulePleaseWait.tr()); await bookAppointmentsViewModel.getDoctorFreeSlots( isBookingForLiveCare: false, onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(), context, child: AppointmentCalendar(), isFullScreen: false, @@ -847,15 +841,14 @@ class _AppointmentDetailsPageState extends State { case 0: break; case 10: - LoaderBottomSheet.showLoader(loadingText: "Confirming Appointment, Please Wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr()); await myAppointmentsViewModel.confirmAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed Successfully".needTranslation), - callBackFunc: (str) { + showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr()), callBackFunc: (str) { Navigator.of(context).pop(); }, title: "", diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 475ee70..a38a9f1 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -90,7 +90,7 @@ class _AppointmentPaymentPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Appointment Payment".needTranslation, + title: LocaleKeys.appointmentPayment.tr(), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -111,7 +111,7 @@ class _AppointmentPaymentPageState extends State { Image.asset(AppAssets.mada, width: 72.h, height: 25.h) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + "Mada".toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], ), SizedBox(width: 8.h), @@ -154,7 +154,6 @@ class _AppointmentPaymentPageState extends State { ).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), "Visa or Mastercard" - .needTranslation .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], @@ -195,7 +194,6 @@ class _AppointmentPaymentPageState extends State { .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), "Tamara" - .needTranslation .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], @@ -250,8 +248,7 @@ class _AppointmentPaymentPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Insurance expired or inactive" - .needTranslation + LocaleKeys.insuranceExpiredOrInactive.tr() .toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500) .paddingSymmetrical(24.h, 0.h), CustomButton( @@ -277,12 +274,12 @@ class _AppointmentPaymentPageState extends State { ) : const SizedBox(), SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr().toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr().toText14(isBold: true), Utils.getPaymentAmountWithSymbol( myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, @@ -293,7 +290,7 @@ class _AppointmentPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + "VAT 15%".toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( myAppointmentsVM.patientAppointmentShareResponseModel!.patientTaxAmount! .toString() @@ -307,7 +304,7 @@ class _AppointmentPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol( myAppointmentsVM.patientAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, @@ -383,7 +380,7 @@ class _AppointmentPaymentPageState extends State { } void checkPaymentStatus() async { - LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr()); if (selectedPaymentMethod == "TAMARA") { await payfortViewModel.checkTamaraPaymentStatus( transactionID: transID, @@ -441,7 +438,7 @@ class _AppointmentPaymentPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -522,7 +519,7 @@ class _AppointmentPaymentPageState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart index 124bf25..f205aeb 100644 --- a/lib/presentation/appointments/appointment_queue_page.dart +++ b/lib/presentation/appointments/appointment_queue_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -7,6 +8,7 @@ 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/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -30,7 +32,7 @@ class AppointmentQueuePage extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Queueing".needTranslation, + title: LocaleKeys.queueing.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(24.0), @@ -57,7 +59,7 @@ class AppointmentQueuePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - labelText: myAppointmentsVM.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, + labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context), backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), ), @@ -66,12 +68,10 @@ class AppointmentQueuePage extends StatelessWidget { ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 10.h), "Hala ${appState!.getAuthenticatedUser()!.firstName}!!!" - .needTranslation .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 8.h), - "Thank you for your patience, here is your queue number." - .needTranslation + LocaleKeys.thankYouForPatience.tr(context: context) .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 8.h), @@ -111,8 +111,7 @@ class AppointmentQueuePage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Serving Now" - .needTranslation + LocaleKeys.servingNow.tr(context: context) .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 18.h), @@ -138,8 +137,8 @@ class AppointmentQueuePage extends StatelessWidget { ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, labelText: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? "Call for vital signs".needTranslation - : "Call for Doctor".needTranslation, + ? LocaleKeys.callForVitalSigns.tr(context: context) + : LocaleKeys.callForDoctor.tr(context: context), iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, @@ -180,35 +179,23 @@ class AppointmentQueuePage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h), SizedBox(width: 8.w), - "Things to ask your doctor today".needTranslation.toText16(isBold: true), + LocaleKeys.thingsToAskDoctor.tr(context: context).toText16(isBold: true), ], ), SizedBox(height: 8.h), - - // What can I do to improve my overall health? - // Are there any routine screenings I should get? - // What is this medication for? - // Are there any side effects I should know about? - // When should I come back for a follow-up? - - "• ${"What can I do to improve my overall health?"}" - .needTranslation + "• ${LocaleKeys.improveOverallHealth.tr(context: context)}" .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 4.h), - "• ${"Are there any routine screenings I should get?"}" - .needTranslation + "• ${LocaleKeys.routineScreenings.tr(context: context)}" .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 4.h), - "• ${"What is this medication for?"}" - .needTranslation + "• ${LocaleKeys.whatIsThisMedicationFor.tr(context: context)}" .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 4.h), - "• ${"Are there any side effects I should know about?"}" - .needTranslation + "• ${LocaleKeys.sideEffectsToKnow.tr(context: context)}" .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 4.h), - "• ${"When should I come back for a follow-up?"}" - .needTranslation + "• ${LocaleKeys.whenFollowUp.tr(context: context)}" .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 16.h), @@ -229,7 +216,7 @@ class AppointmentQueuePage extends StatelessWidget { hasShadow: true, ), child: CustomButton( - text: "Go to homepage".needTranslation, + text: LocaleKeys.goToHomepage.tr(context: context), onPressed: () { Navigator.pushAndRemoveUntil( context, @@ -249,11 +236,10 @@ class AppointmentQueuePage extends StatelessWidget { icon: AppAssets.homeBottom, iconColor: AppColors.whiteColor, iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h), - ) + ).paddingSymmetrical(24.h, 24.h), + ), ], ); - }), - ); + })); } } diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index b4c3630..1209467 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -56,7 +56,7 @@ class _MyAppointmentsPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Appointments List".needTranslation, + title: LocaleKeys.appointmentsList.tr(context: context), child: SingleChildScrollView( child: Column( children: [ @@ -65,9 +65,9 @@ class _MyAppointmentsPageState extends State { activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ - CustomTabBarModel(null, "All Appt.".needTranslation), - CustomTabBarModel(null, "Upcoming".needTranslation), - CustomTabBarModel(null, "Completed".needTranslation), + CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)), + CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)), + CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)), ], onTabChange: (index) { setState(() { @@ -248,7 +248,7 @@ class _MyAppointmentsPageState extends State { ) : Utils.getNoDataWidget( context, - noDataText: "You don't have any appointments yet.".needTranslation, + noDataText: LocaleKeys.noAppointmentsYet.tr(context: context), callToActionButton: CustomButton( text: LocaleKeys.bookAppo.tr(context: context), onPressed: () { @@ -296,7 +296,7 @@ class _MyAppointmentsPageState extends State { onClicked: () { if (myAppointmentsVM.availableFilters[index] == AppointmentListingFilters.DATESELECTION) { showCommonBottomSheetWithoutHeight( - title: "Set The Date Range".needTranslation, + title: LocaleKeys.setTheDateRange.tr(context: context), context, child: DateRangeSelector( onRangeSelected: (start, end) { diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 2c5d1b0..3795ce1 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -211,7 +211,7 @@ class _MyDoctorsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AppCustomChipWidget(labelText: "${group.length} ${'doctors'.needTranslation}"), + AppCustomChipWidget(labelText: "${group.length} ${'doctors'}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), @@ -284,7 +284,7 @@ class _MyDoctorsPageState extends State { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: "View Profile".needTranslation.tr(context: context), + text: LocaleKeys.viewProfile.tr(context: context), onPressed: () async { bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( clinicID: doctor?.clinicID ?? 0, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 39e4e03..6b0439f 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -96,13 +96,13 @@ class AppointmentCard extends StatelessWidget { AppCustomChipWidget( icon: isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), iconColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), - labelText: isLoading ? 'Walk In'.needTranslation : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : 'Walk In'.needTranslation), + labelText: isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), backgroundColor: isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: isLoading - ? 'OutPatient'.needTranslation + ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), @@ -111,7 +111,7 @@ class AppointmentCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: isLoading - ? 'Booked'.needTranslation + ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, @@ -229,7 +229,7 @@ class AppointmentCard extends StatelessWidget { return SizedBox.shrink(); } else { return CustomButton( - text: 'Select appointment'.needTranslation, + text: LocaleKeys.selectAppointment.tr(context: context), onPressed: () { if (isForFeedback) { contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); @@ -310,7 +310,7 @@ class AppointmentCard extends StatelessWidget { return CustomButton( text: LocaleKeys.askDoctor.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); await myAppointmentsViewModel.isDoctorAvailable( projectID: patientAppointmentHistoryResponseModel.projectID, doctorId: patientAppointmentHistoryResponseModel.doctorID, @@ -353,7 +353,7 @@ class AppointmentCard extends StatelessWidget { } return CustomButton( - text: 'Rebook with same doctor'.needTranslation, + text: LocaleKeys.rebookSameDoctor.tr(context: context), onPressed: () => openDoctorScheduleCalendar(context), backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, @@ -417,7 +417,7 @@ class AppointmentCard extends StatelessWidget { context, child: AppointmentCalendar(), callBackFunc: () {}, - title: 'Pick a Date'.needTranslation, + title: LocaleKeys.pickADate.tr(context: context), isFullScreen: false, isCloseButtonVisible: true, ); diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index d118a5e..c5139f1 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -45,8 +45,8 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { children: [ checkInOptionCard( AppAssets.checkin_location_icon, - "Live Location".needTranslation, - "Verify your location to be at hospital to check in".needTranslation, + LocaleKeys.liveLocation.tr(context: context), + LocaleKeys.verifyYourLocationAtHospital.tr(context: context), ).onPress(() { // locationUtils = LocationUtils( // isShowConfirmDialog: false, @@ -61,8 +61,10 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { sendCheckInRequest(projectDetailListModel.checkInQrCode!, 3, context); } else { showCommonBottomSheetWithoutHeight(context, - title: "Error".needTranslation, - child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + title: LocaleKeys.error.tr(context: context), + child: Utils.getErrorWidget( + loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context), + ), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); } @@ -71,8 +73,8 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_nfc_icon, - "NFC (Near Field Communication)".needTranslation, - "Scan your phone via NFC board to check in".needTranslation, + LocaleKeys.nfcNearFieldCommunication.tr(context: context), + LocaleKeys.scanPhoneViaNFC.tr(context: context), ).onPress(() { Future.delayed(const Duration(milliseconds: 500), () { showNfcReader(context, onNcfScan: (String nfcId) { @@ -85,8 +87,8 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_qr_icon, - "QR Code".needTranslation, - "Scan QR code with your camera to check in".needTranslation, + LocaleKeys.qrCode.tr(context: context), + LocaleKeys.scanQRCodeToCheckIn.tr(context: context), ).onPress(() async { String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); if (onlineCheckInQRCode != "") { @@ -139,14 +141,16 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { } void sendCheckInRequest(String scannedCode, int checkInType, BuildContext context) async { - LoaderBottomSheet.showLoader(loadingText: "Processing Check-In...".needTranslation); + LoaderBottomSheet.showLoader( + loadingText: LocaleKeys.processingCheckIn.tr(context: context), + ); await myAppointmentsViewModel.sendCheckInNfcRequest( patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType, onSuccess: (apiResponse) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( @@ -164,7 +168,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { }, onError: (error) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); }, diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index ccf6674..0c4aec1 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -114,7 +114,7 @@ class AppointmentDoctorCard extends StatelessWidget { iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) - : "Walk In".needTranslation, + : LocaleKeys.walkin.tr(context: context), backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, @@ -160,7 +160,7 @@ class AppointmentDoctorCard extends StatelessWidget { iconColor: AppColors.primaryRedColor, ) : CustomButton( - text: "Rebook with same doctor".needTranslation, + text: LocaleKeys.rebookSameDoctor.tr(), onPressed: () { onRescheduleTap(); }, diff --git a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart index 01aab4e..376c27a 100644 --- a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart +++ b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart @@ -105,7 +105,7 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon.".needTranslation), + child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon."), callBackFunc: () { Navigator.of(context).pop(); }, diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart index cb27f9f..b6366db 100644 --- a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart @@ -46,7 +46,7 @@ class FacilityTypeSelectionWidget extends StatelessWidget { SizedBox(height: 24.h), FacilitySelectionItem( svgPath: AppAssets.hmg, - title: "HMG".needTranslation, + title: "HMG", subTitle: LocaleKeys.hospitalsWithCount.tr(namedArgs: { 'count': "${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmgSize ?? 0}" @@ -63,7 +63,7 @@ class FacilityTypeSelectionWidget extends StatelessWidget { SizedBox(height: 16.h), FacilitySelectionItem( svgPath: AppAssets.hmc, - title: "HMC".needTranslation, + title: "HMC", subTitle: LocaleKeys.medicalCentersWithCount.tr(namedArgs: { 'count': "${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}" diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart index b01b541..5d9ed8d 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart @@ -76,7 +76,7 @@ class HospitalListItem extends StatelessWidget { Visibility( visible: (hospitalData?.distanceInKMs != "0"), child: AppCustomChipWidget( - labelText: "${hospitalData?.distanceInKMs ?? ""} km".needTranslation, + labelText: "${hospitalData?.distanceInKMs ?? ""} km", deleteIcon: AppAssets.location_red, deleteIconSize: Size(9, 12), backgroundColor: AppColors.secondaryLightRedColor, @@ -88,7 +88,7 @@ class HospitalListItem extends StatelessWidget { child: Row( children: [ AppCustomChipWidget( - labelText: "Distance not available".needTranslation, + labelText: "Distance not available", textColor: AppColors.blackColor, ), // SizedBox( @@ -99,7 +99,7 @@ class HospitalListItem extends StatelessWidget { Visibility( visible: !isLocationEnabled, child: AppCustomChipWidget( - labelText: "Location turned off".needTranslation, + labelText: "Location turned off", deleteIcon: AppAssets.location_unavailable, deleteIconSize: Size(9.w, 12.h), textColor: AppColors.blackColor, diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart index cbf68f6..e9a5e36 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart @@ -23,7 +23,7 @@ class TypeSelectionWidget extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ AppCustomChipWidget( - labelText: "All Facilities".needTranslation, + labelText: "All Facilities", shape: RoundedRectangleBorder( side: BorderSide( color: data.currentlySelectedFacility == FacilitySelection.ALL @@ -45,7 +45,7 @@ class TypeSelectionWidget extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.hmg, iconHasColor: false, - labelText: "Hospitals".needTranslation, + labelText: "Hospitals", shape: RoundedRectangleBorder( side: BorderSide( color: data.currentlySelectedFacility == FacilitySelection.HMG @@ -67,7 +67,7 @@ class TypeSelectionWidget extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.hmc, iconHasColor: false, - labelText: "Medical Centers".needTranslation, + labelText: "Medical Centers", shape: RoundedRectangleBorder( side: BorderSide( color: data.currentlySelectedFacility == FacilitySelection.HMC diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index c14e957..a59062d 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -87,7 +87,7 @@ class LoginScreenState extends State { isAllowLeadingIcon: true, padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), leadingIcon: AppAssets.student_card, - errorMessage: "Please enter a valid national ID or file number".needTranslation, + errorMessage: LocaleKeys.enterValidIDorIqama.tr(), hasError: false, ), SizedBox(height: 16.h), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index f04f7dd..fa10b95 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -112,7 +112,7 @@ class _RegisterNew extends State { Divider(height: 1), TextInputWidget( labelText: LocaleKeys.dob.tr(), - hintText: "11 July, 1994".needTranslation, + hintText: "11 July, 1994", controller: authVm.dobController, focusNode: _dobFocusNode, isEnable: true, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index bcb2131..39d5eee 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -89,8 +89,8 @@ class _BookAppointmentPageState extends State { activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), initialIndex: bookAppointmentsVM.selectedTabIndex, tabs: [ - CustomTabBarModel(null, "General".needTranslation), - CustomTabBarModel(null, "LiveCare".needTranslation), + CustomTabBarModel(null, LocaleKeys.general.tr()), + CustomTabBarModel(null, LocaleKeys.liveCare.tr()), ], onTabChange: (index) { bookAppointmentsVM.onTabChanged(index); @@ -121,7 +121,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (appState.isAuthenticated) ...[], - "Recent Visits".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), + LocaleKeys.recentVisits.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0.h), SizedBox(height: 16.h), SizedBox( height: 110.h, @@ -232,8 +232,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Search By Clinic".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByClinic.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -264,8 +264,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Search By Doctor".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByDoctor.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelect.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -294,8 +294,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Search By Region".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Central Region".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByRegion.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.centralRegion.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -340,8 +340,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.immediateConsultation.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -382,8 +382,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.scheduledConsultation.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.tapToSelectClinic.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -412,8 +412,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.pharmaLiveCare.tr().toText14(color: AppColors.textColor, weight: FontWeight.w500), + "".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), ], ), ], @@ -447,9 +447,9 @@ class _BookAppointmentPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Not sure? help me choose a clinic!".needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor), - SizedBox(height: 4.h), - "Mention your symptoms and find the list of doctors accordingly".needTranslation.toText12( + LocaleKeys.notSureHelpMeChooseClinic.tr().toText16(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 8.h), + LocaleKeys.mentionYourSymptomsAndFindDoctors.tr().toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), @@ -558,8 +558,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.immediateService.tr().toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noNeedToWaitGetMedicalConsultation.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -574,7 +574,7 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "No visit required".needTranslation.toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noVisitRequired.tr().toText18(color: AppColors.textColor, isBold: true), LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), @@ -590,8 +590,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillContact.tr().toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.specialisedDoctorWillContactYou.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -606,8 +606,8 @@ class _BookAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.freeMedicineDelivery.tr().toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.offersFreeMedicineDelivery.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -615,7 +615,7 @@ class _BookAppointmentPageState extends State { ), SizedBox(height: 36.h), CustomButton( - text: "Login to use this service".needTranslation, + text: "Login to use this service", onPressed: () async { await authVM.onLoginPressed(); }, diff --git a/lib/presentation/book_appointment/dental_chief_complaints_page.dart b/lib/presentation/book_appointment/dental_chief_complaints_page.dart index 4dc3881..d2f2239 100644 --- a/lib/presentation/book_appointment/dental_chief_complaints_page.dart +++ b/lib/presentation/book_appointment/dental_chief_complaints_page.dart @@ -1,5 +1,6 @@ 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'; @@ -9,6 +10,7 @@ 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/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/chief_complaint_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -40,7 +42,7 @@ class _DentalChiefComplaintsPageState extends State { bookAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return CollapsingListView( - title: "Dental Chief Complaints".needTranslation, + title: LocaleKeys.dentalChiefComplaints.tr(), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), diff --git a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart index e4d11bd..d9aa9e5 100644 --- a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart +++ b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart @@ -128,7 +128,7 @@ class DoctorsFilters extends StatelessWidget{ TextInputWidget( controller: TextEditingController()..text =context.watch().selectedClinicForFilters ??'', labelText: LocaleKeys.clinicName.tr(context: context), - hintText: LocaleKeys.searchClinic.tr().needTranslation, + hintText: LocaleKeys.searchClinic.tr(), isEnable: false, prefix: null, autoFocus: false, diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 5ce7fac..72f242d 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -1,4 +1,5 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -8,6 +9,7 @@ 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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -33,7 +35,7 @@ class DoctorProfilePage extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Doctor Profile".needTranslation, + title: LocaleKeys.doctorProfile.tr(), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -84,11 +86,11 @@ class DoctorProfilePage extends StatelessWidget { children: [ AppCustomChipWidget( iconColor: AppColors.ratingColorYellow, - labelText: "Branch: ${bookAppointmentsViewModel.doctorsProfileResponseModel.projectName}".needTranslation, + labelText: "${bookAppointmentsViewModel.doctorsProfileResponseModel.projectName}", ), AppCustomChipWidget( iconColor: AppColors.ratingColorYellow, - labelText: "Clinic: ${bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription}".needTranslation, + labelText: "${bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription}", ), ], ), @@ -142,7 +144,7 @@ class DoctorProfilePage extends StatelessWidget { hasShadow: true, ), child: CustomButton( - text: "View available appointments".needTranslation, + text: LocaleKeys.viewAvailableAppointments.tr(), onPressed: () async { LoaderBottomSheet.showLoader(); bookAppointmentsViewModel.isLiveCareSchedule @@ -151,7 +153,7 @@ class DoctorProfilePage extends StatelessWidget { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(), context, child: AppointmentCalendar(), isFullScreen: false, @@ -174,7 +176,7 @@ class DoctorProfilePage extends StatelessWidget { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(), context, child: AppointmentCalendar(), isFullScreen: false, diff --git a/lib/presentation/book_appointment/laser/laser_appointment.dart b/lib/presentation/book_appointment/laser/laser_appointment.dart index aae7990..3b94b0e 100644 --- a/lib/presentation/book_appointment/laser/laser_appointment.dart +++ b/lib/presentation/book_appointment/laser/laser_appointment.dart @@ -83,8 +83,8 @@ class LaserAppointment extends StatelessWidget { activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ - CustomTabBarModel(null,LocaleKeys.malE.tr()), - CustomTabBarModel(null, "Female".needTranslation), + CustomTabBarModel(null, LocaleKeys.malE.tr()), + CustomTabBarModel(null, "Female"), ], onTabChange: (index) { var viewmodel = context.read(); diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 2371f4a..3e48f8b 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -45,7 +45,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Review LiveCare Request".needTranslation, + title: LocaleKeys.reviewLiveCareRequest.tr(context: context), child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 24.h), child: Column( @@ -80,7 +80,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { spacing: 3.h, runSpacing: 4.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old".needTranslation), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), AppCustomChipWidget( labelText: "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceName)!}"), @@ -93,7 +93,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ), ), SizedBox(height: 24.h), - "Selected LiveCare Type".needTranslation.toText16(isBold: true), + LocaleKeys.selectedLiveCareType.tr(context: context).toText16(isBold: true), SizedBox(height: 16.h), Consumer(builder: (context, bookAppointmentsVM, child) { return Container( @@ -111,7 +111,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), SizedBox(width: 8.h), - getLiveCareType(immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true), + getLiveCareType(context, immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true), ], ), Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), @@ -121,7 +121,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ).onPress(() { showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async { debugPrint("Selected Call Type: ${immediateLiveCareViewModel.liveCareSelectedCallType}"); - }, title: "Select LiveCare call type".needTranslation, isCloseButtonVisible: true, isFullScreen: false); + }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); }); }), SizedBox(height: 24.h) @@ -152,7 +152,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { @@ -176,12 +176,12 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ) : const SizedBox(), SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), ], @@ -189,7 +189,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), @@ -210,7 +210,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { onPressed: () async { await askVideoCallPermission().then((val) async { if (val) { - LoaderBottomSheet.showLoader(loadingText: "Confirming LiveCare request, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); await immediateLiveCareViewModel.getPatientLiveCareHistory(); @@ -230,7 +230,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Unknown error occurred...".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.unknownErrorOccurred.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -241,9 +241,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: - "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed." - .needTranslation, + loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -285,9 +283,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: - "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed." - .needTranslation, + loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -351,16 +347,16 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { // } } - String getLiveCareType(int callType) { + String getLiveCareType(BuildContext context, int callType) { switch (callType) { case 1: - return "Video Call".needTranslation; + return LocaleKeys.videoCall.tr(context: context); case 2: - return "Audio Call".needTranslation; + return LocaleKeys.audioCall.tr(context: context); case 3: - return "Phone Call".needTranslation; + return LocaleKeys.phoneCall.tr(context: context); default: - return "Video Call".needTranslation; + return LocaleKeys.videoCall.tr(context: context); } } } diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 48e79b1..9ae7ee3 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -84,7 +84,7 @@ class _ImmediateLiveCarePaymentPageState extends State { runSpacing: 8.h, children: [ AppCustomChipWidget( - labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}".needTranslation, + labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}", ), AppCustomChipWidget( - labelText: "${LocaleKeys.branch.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation, + labelText: "${LocaleKeys.branch.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.projectName}", ), AppCustomChipWidget( labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}" - .needTranslation, + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}", ), AppCustomChipWidget( labelText: - "${LocaleKeys.time.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? "Waiting Appointment".needTranslation : bookAppointmentsViewModel.selectedAppointmentTime}" - .needTranslation, + "${LocaleKeys.time.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? LocaleKeys.waitingAppointment.tr(context: context) : bookAppointmentsViewModel.selectedAppointmentTime}", ), ], ), @@ -166,7 +164,7 @@ class _ReviewAppointmentPageState extends State { ), ), SizedBox(height: 24.h), - "Hospital Information".needTranslation.toText16(isBold: true), + LocaleKeys.hospitalInformation.tr(context: context).toText16(isBold: true), SizedBox(height: 16.h), Container( width: double.infinity, @@ -241,7 +239,7 @@ class _ReviewAppointmentPageState extends State { } void getWalkInAppointmentPatientShare() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment Share...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentShare.tr(context: context)); await bookAppointmentsViewModel.getWalkInPatientShareAppointment(onSuccess: (val) { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( @@ -262,7 +260,7 @@ class _ReviewAppointmentPageState extends State { } void initiateBookAppointment() async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 9f2da16..fa871f5 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -52,7 +52,7 @@ class _SearchDoctorByNameState extends State { children: [ Expanded( child: CollapsingListView( - title: "Choose Doctor".needTranslation, + title: LocaleKeys.chooseDoctor.tr(), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -228,7 +228,7 @@ class _SearchDoctorByNameState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomButton( - text: "${groupedDoctors[index].length} ${'doctors'.needTranslation}", + text: "${groupedDoctors[index].length} ${'doctors'}", onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 15c8654..e76e1c9 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -103,7 +103,7 @@ class _SelectClinicPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), + title: bookAppointmentsViewModel.isLiveCareSchedule ? LocaleKeys.selectLiveCareClinic.tr(context: context) : LocaleKeys.selectClinic.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -1114,18 +1114,18 @@ class _SelectClinicPageState extends State { void initDentalAppointmentBookingFlow(int projectID) async { bookAppointmentsViewModel.setProjectID(projectID.toString()); - LoaderBottomSheet.showLoader(loadingText: "Checking for an existing dental plan, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingForExistingDentalPlan.tr(context: context)); await bookAppointmentsViewModel.getPatientDentalEstimation(projectID: projectID).then((value) { LoaderBottomSheet.hideLoader(); if (bookAppointmentsViewModel.patientDentalPlanEstimationList.isNotEmpty) { showCommonBottomSheetWithoutHeight( // title: LocaleKeys.notice.tr(context: context), - title: "Dental treatment plan".needTranslation, + title: LocaleKeys.dentalTreatmentPlan.tr(context: context), context, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "You have an existing treatment plan: ".needTranslation.toText14(weight: FontWeight.w500), + LocaleKeys.youHaveExistingTreatmentPlan.tr(context: context).toText14(weight: FontWeight.w500), SizedBox(height: 8.h), Container( width: double.infinity, @@ -1156,7 +1156,7 @@ class _SelectClinicPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} Mins".needTranslation), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), ], ); }, @@ -1171,15 +1171,15 @@ class _SelectClinicPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Total time required".needTranslation.toText14(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 Mins".needTranslation), + LocaleKeys.totalTimeRequired.tr(context: context).toText14(isBold: true), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 ${LocaleKeys.mins.tr(context: context)}"), ], ) ], ), ), SizedBox(height: 16.h), - "Would you like to continue it?".needTranslation.toText14(weight: FontWeight.w500), + LocaleKeys.wouldYouLikeToContinue.tr(context: context).toText14(weight: FontWeight.w500), SizedBox(height: 16.h), Row( children: [ diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 2f8747d..e57ed51 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: "Choose Doctor".needTranslation, + title: LocaleKeys.chooseDoctor.tr(), // 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), @@ -178,7 +178,7 @@ class _SelectDoctorPageState extends State { children: [ LocaleKeys.nearestAppo.tr(context: context).toText13(isBold: true), SizedBox(height: 4.h), - "View nearest available appointments".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.viewNearestAppos.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), const Spacer(), @@ -207,7 +207,7 @@ class _SelectDoctorPageState extends State { bookAppointmentsViewModel: bookAppointmentsViewModel, ) : bookAppointmentsVM.doctorsListGrouped.isEmpty - ? Utils.getNoDataWidget(context, noDataText: "No Doctor found for selected criteria...".needTranslation) + ? Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDoctorFound.tr()) : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), @@ -245,7 +245,7 @@ class _SelectDoctorPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomButton( - text: "${bookAppointmentsVM.doctorsListGrouped[index].length} ${'doctors'.needTranslation}", + text: "${bookAppointmentsVM.doctorsListGrouped[index].length} ${'doctors'}", onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 502e38d..87ab0eb 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -54,8 +54,8 @@ class SelectLivecareClinicPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.immediateService.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -70,7 +70,7 @@ class SelectLivecareClinicPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "No visit required".needTranslation.toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.noVisitRequired.tr(context: context).toText18(color: AppColors.textColor, isBold: true), LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), @@ -86,8 +86,8 @@ class SelectLivecareClinicPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillContact.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -102,8 +102,8 @@ class SelectLivecareClinicPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.freeMedicineDelivery.tr(context: context).toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -117,7 +117,7 @@ class SelectLivecareClinicPage extends StatelessWidget { Column( children: [ CustomButton( - text: "Yes please, I am in a hurry".needTranslation, + text: LocaleKeys.yesPleasImInAHurry.tr(context: context), onPressed: () async { Navigator.pop(context); GetLiveCareClinicListResponseModel liveCareClinic = GetLiveCareClinicListResponseModel( @@ -129,7 +129,7 @@ class SelectLivecareClinicPage extends StatelessWidget { immediateLiveCareViewModel.setLiveCareSelectedCallType(1); immediateLiveCareViewModel.setImmediateLiveCareSelectedClinic(liveCareClinic); - LoaderBottomSheet.showLoader(loadingText: "Fetching fees, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr(context: context)); await immediateLiveCareViewModel.getLiveCareImmediateAppointmentFees(onSuccess: (val) { LoaderBottomSheet.hideLoader(); Navigator.of(getIt.get().navigatorKey.currentContext!).push( @@ -162,7 +162,7 @@ class SelectLivecareClinicPage extends StatelessWidget { ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), CustomButton( - text: "No, Thanks. I would like a physical visit".needTranslation, + text: LocaleKeys.noThanksPhysicalVisit.tr(context: context), onPressed: () { Navigator.of(context).pop(); onNegativeClicked?.call(); diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart index f832db6..19d0fb4 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart @@ -28,7 +28,7 @@ class WaitingAppointmentInfo extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Waiting Appointment".needTranslation, + title: LocaleKeys.waitingAppointment.tr(), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -47,13 +47,11 @@ class WaitingAppointmentInfo extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.waiting_appointment_icon, width: 48.h, height: 48.h, fit: BoxFit.contain), SizedBox(height: 16.h), - "What is Waiting Appointment?".needTranslation.toText16(isBold: true), + LocaleKeys.whatIsWaitingAppointment.tr(context: context).toText16(isBold: true), SizedBox(height: 16.h), - "The waiting appointments feature allows you to book an appointment while you are inside the hospital building, and in case there is no available slot in the doctor’s schedule." - .needTranslation - .toText14(isBold: false), + LocaleKeys.waitingAppointmentsFeature.tr(context: context).toText14(isBold: false), SizedBox(height: 16.h), - "The appointment with the doctor is confirmed, but the time of entry is uncertain.".needTranslation.toText14(isBold: false), + LocaleKeys.appointmentWithDoctorConfirmed.tr(context: context).toText14(isBold: false), SizedBox(height: 24.h), Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -66,9 +64,7 @@ class WaitingAppointmentInfo extends StatelessWidget { SizedBox(width: 10.w), SizedBox( width: MediaQuery.of(context).size.width * 0.7, - child: "Note: You must have to pay within 10 minutes of booking, otherwise your appointment will be cancelled automatically" - .needTranslation - .toText14(isBold: true, color: AppColors.warningColorYellow), + child: LocaleKeys.paymentWithinTenMinutes.tr(context: context).toText14(isBold: true, color: AppColors.warningColorYellow), ), ], ), @@ -88,7 +84,7 @@ class WaitingAppointmentInfo extends StatelessWidget { hasShadow: true, ), child: CustomButton( - text: "Continue".needTranslation, + text: LocaleKeys.continueString.tr(), onPressed: () async { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.onlineCheckIn.tr(), diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart index 4a2a304..6f3a773 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -10,6 +11,7 @@ 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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/review_appointment_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:barcode_scan2/barcode_scan2.dart'; @@ -42,14 +44,9 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { children: [ checkInOptionCard( AppAssets.checkin_location_icon, - "Live Location".needTranslation, - "Verify your location to be at hospital to check in".needTranslation, + LocaleKeys.liveLocation.tr(), + LocaleKeys.verifyYourLocationAtHospital.tr(), ).onPress(() { - // locationUtils = LocationUtils( - // isShowConfirmDialog: false, - // navigationService: myAppointmentsViewModel.navigationService, - // appState: myAppointmentsViewModel.appState, - // ); locationUtils.getCurrentLocation(onSuccess: (value) { projectDetailListModel = Utils.getProjectDetailObj(appState, bookAppointmentsViewModel.waitingAppointmentProjectID); double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; @@ -58,8 +55,8 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { checkScannedNFCAndQRCode(projectDetailListModel.checkInQrCode!, context); } else { showCommonBottomSheetWithoutHeight(context, - title: "Error".needTranslation, - child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + title: LocaleKeys.error.tr(), + child: Utils.getErrorWidget(loadingText: LocaleKeys.ensureWithinHospitalLocation.tr()), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); } @@ -68,8 +65,8 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_nfc_icon, - "NFC (Near Field Communication)".needTranslation, - "Scan your phone via NFC board to check in".needTranslation, + LocaleKeys.nfcNearFieldCommunication.tr(), + LocaleKeys.scanPhoneViaNFC.tr(), ).onPress(() { Future.delayed(const Duration(milliseconds: 500), () { showNfcReader(context, onNcfScan: (String nfcId) { @@ -82,8 +79,8 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_qr_icon, - "QR Code".needTranslation, - "Scan QR code with your camera to check in".needTranslation, + LocaleKeys.qrCode.tr(), + LocaleKeys.scanQRCodeToCheckIn.tr() ).onPress(() async { String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); if (onlineCheckInQRCode != "") { @@ -136,7 +133,7 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { } void checkScannedNFCAndQRCode(String scannedCode, BuildContext context) async { - LoaderBottomSheet.showLoader(loadingText: "Processing Check-In...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.processingCheckIn.tr()); bookAppointmentsViewModel.checkScannedNFCAndQRCode( scannedCode, bookAppointmentsViewModel.waitingAppointmentProjectID, @@ -152,7 +149,7 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { }, onError: (err) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: err), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(), child: Utils.getErrorWidget(loadingText: err), callBackFunc: () { // Navigator.of(context).pop(); }, isFullScreen: false); }, diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart index 8cfe6dd..ca60e9e 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart @@ -94,7 +94,7 @@ class _WaitingAppointmentPaymentPageState extends State { ), SizedBox(height: 16.h), CustomButton( - text: "Select".needTranslation, + text: LocaleKeys.select.tr(context: context), onPressed: () async { if (appState.isAuthenticated) { - if(selectedTime == "Waiting Appointment".needTranslation){ + if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ bookAppointmentsViewModel.setWaitingAppointmentProjectID(bookAppointmentsViewModel.selectedDoctor.projectID!); bookAppointmentsViewModel.setWaitingAppointmentDoctor(bookAppointmentsViewModel.selectedDoctor); @@ -293,7 +293,7 @@ class _AppointmentCalendarState extends State { dayEvents.clear(); DateTime dateStartObj = new DateTime(dateStart.year, dateStart.month, dateStart.day, 0, 0, 0, 0, 0); if (bookAppointmentsViewModel.isWaitingAppointmentAvailable && DateUtils.isSameDay(dateStart, DateTime.now())) { - dayEvents.add(TimeSlot(isoTime: "Waiting Appointment".needTranslation, start: DateTime.now(), end: DateTime.now(), vidaDate: "")); + dayEvents.add(TimeSlot(isoTime: LocaleKeys.waitingAppointment.tr(context: context), start: DateTime.now(), end: DateTime.now(), vidaDate: "")); } freeSlots.forEach((v) { if (v.start == dateStartObj) dayEvents.add(v); @@ -332,7 +332,7 @@ class TimeSlotChip extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: onTap, - child: label == "Waiting Appointment".needTranslation + child: label == LocaleKeys.waitingAppointment.tr(context: context) ? Container( padding: EdgeInsets.symmetric(horizontal: 14.h, vertical: 8.h), decoration: ShapeDecoration( diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index d2e1c0b..97a941d 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -137,15 +137,15 @@ class DoctorCard extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, + labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, + labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}", ).toShimmer2(isShow: isLoading), bookAppointmentsViewModel.isNearestAppointmentSelected ? doctorsListResponseModel.nearestFreeSlot != null ? AppCustomChipWidget( - labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)).needTranslation, + labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), backgroundColor: AppColors.successColor, textColor: AppColors.whiteColor, ).toShimmer2(isShow: isLoading) @@ -165,7 +165,7 @@ class DoctorCard extends StatelessWidget { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(context: context), context, child: AppointmentCalendar(), isFullScreen: false, @@ -188,7 +188,7 @@ class DoctorCard extends StatelessWidget { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(context: context), context, child: AppointmentCalendar(), isFullScreen: false, diff --git a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart index 7547fd0..dd836a6 100644 --- a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart @@ -10,6 +10,7 @@ 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/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -124,7 +125,7 @@ class _CmcOrderDetailPageState extends State { Row( children: [ if (!isLoading) ...[ - "Request ID:".needTranslation.toText14( + LocaleKeys.requestID.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -164,7 +165,7 @@ class _CmcOrderDetailPageState extends State { children: [ Expanded( child: CustomButton( - text: "Cancel Order".needTranslation, + text: LocaleKeys.cancelOrder.tr(context: context), onPressed: isLoading ? () {} : () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -196,7 +197,7 @@ class _CmcOrderDetailPageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any CMC orders yet.".needTranslation, + noDataText: LocaleKeys.noCMCOrdersYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -209,7 +210,7 @@ class _CmcOrderDetailPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "CMC Orders".needTranslation, + title: LocaleKeys.cmcOrders.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart index 98e91b8..93c8d5f 100644 --- a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart @@ -55,7 +55,7 @@ class CmcHospitalBottomSheetBody extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Choose your preferred hospital for the service".needTranslation.toText14( + LocaleKeys.choosePreferredHospitalForService.tr(context: context).toText14( weight: FontWeight.w400, color: AppColors.greyTextColor, letterSpacing: -0.4, @@ -85,7 +85,7 @@ class CmcHospitalBottomSheetBody extends StatelessWidget { ? _buildLoadingShimmer() : hmgServicesViewModel.filteredHospitalsList.isEmpty ? Center( - child: "No hospitals Found".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + child: LocaleKeys.noHospitalsFound.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), ) : ListView.separated( itemCount: hmgServicesViewModel.filteredHospitalsList.length, diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart index 39d6d7c..25cd506 100644 --- a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart @@ -97,7 +97,7 @@ class CmcHospitalListItem extends StatelessWidget { Visibility( visible: (hospital.distanceInKilometers != null && hospital.distanceInKilometers! > 0), child: AppCustomChipWidget( - labelText: "$distanceText km".needTranslation, + labelText: "$distanceText km", icon: AppAssets.location_red, iconColor: AppColors.errorColor, backgroundColor: AppColors.secondaryLightRedColor, @@ -107,7 +107,7 @@ class CmcHospitalListItem extends StatelessWidget { Visibility( visible: (hospital.distanceInKilometers == null || hospital.distanceInKilometers == 0), child: AppCustomChipWidget( - labelText: " Distance not available".needTranslation, + labelText: " Distance not available", textColor: AppColors.blackColor, ), ), diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart index 908aac9..fcce480 100644 --- a/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart @@ -24,7 +24,7 @@ class CmcUiSelectionHelper { showCommonBottomSheetWithoutHeight( context, - title: "Select Hospital".needTranslation, + title: LocaleKeys.selectHospital.tr(context: context), child: CmcHospitalBottomSheetBody( onHospitalSelected: (hospital) { hmgServicesViewModel.setSelectedHospitalForOrder(hospital); @@ -44,7 +44,7 @@ class CmcUiSelectionHelper { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to cancel this order?".needTranslation, + loadingText: LocaleKeys.cancelOrderConfirmation.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -70,7 +70,7 @@ class CmcUiSelectionHelper { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.orderCancelledSuccessfully.tr(context: context)), SizedBox(height: 24.h), Row( children: [ From 8c92df8648f001add6b70acfb10bc06f909352ca Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 14 Jan 2026 09:56:25 +0300 Subject: [PATCH 37/46] contact us page fix --- lib/core/api_consts.dart | 3 +- lib/core/dependencies.dart | 15 +++---- lib/features/contact_us/contact_us_repo.dart | 41 +++++++++++++++++++ .../contact_us/contact_us_view_model.dart | 28 +++++++++++++ .../hmg_services/hmg_services_repo.dart | 28 ++++++++++++- .../contact_us/live_chat_page.dart | 36 +++++++++++++--- lib/routes/app_routes.dart | 35 ++++++++++------ 7 files changed, 160 insertions(+), 26 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fe0b4d5..4acf486 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -151,6 +151,7 @@ var GET_FINDUS_REQUEST = 'Services/Lists.svc/REST/Get_HMG_Locations'; ///LiveChat var GET_LIVECHAT_REQUEST = 'Services/Patients.svc/REST/GetPatientICProjects'; +var GET_LIVECHAT_REQUEST_ID = 'Services/Patients.svc/REST/Patient_ICChatRequest_Insert'; ///babyInformation var GET_BABYINFORMATION_REQUEST = 'Services/Community.svc/REST/GetBabyByUserID'; @@ -661,7 +662,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index bfbccfc..bdcc818 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart'; +import 'package:hmg_patient_app_new/features/monthly_reports/monthly_reports_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; @@ -162,13 +163,13 @@ class AppDependencies { ),); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); + // getIt.registerFactory( + // () => QrParkingViewModel( + // qrParkingRepo: getIt(), + // errorHandlerService: getIt(), + // cacheService: getIt(), + // ), + // ); // ViewModels // Global/shared VMs → LazySingleton diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index 3e96f91..5b9057b 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -14,6 +14,8 @@ abstract class ContactUsRepo { Future>>> getLiveChatProjectsList(); + Future>> getChatRequestID({required String name, required String mobileNo, required String workGroup}); + Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}); } @@ -97,6 +99,45 @@ class ContactUsRepoImp implements ContactUsRepo { } } + @override + Future>> getChatRequestID({required String name, required String mobileNo, required String workGroup}) async { + Map body = {}; + body['Name'] = name; + body['MobileNo'] = mobileNo; + body['WorkGroup'] = workGroup; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECHAT_REQUEST_ID, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final requestId = response['RequestId'] as String; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: requestId, + ); + } 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>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}) async { final Map body = requestInsertCOCItem.toJson(); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 1185700..1029802 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -29,6 +29,8 @@ class ContactUsViewModel extends ChangeNotifier { int selectedLiveChatProjectIndex = -1; + String? chatRequestID; + List feedbackAttachmentList = []; PatientAppointmentHistoryResponseModel? patientFeedbackSelectedAppointment; @@ -153,6 +155,32 @@ class ContactUsViewModel extends ChangeNotifier { ); } + Future getChatRequestID({required String name, required String mobileNo, required String workGroup, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await contactUsRepo.getChatRequestID(name: name, mobileNo: mobileNo, workGroup: workGroup); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } else if (apiResponse.messageStatus == 1) { + chatRequestID = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async { RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem(); requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : ""; diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index 85e6018..e4b9a03 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -940,7 +940,16 @@ class HmgServicesRepoImp implements HmgServicesRepo { for (var vitalSignJson in vitalSignsList) { if (vitalSignJson is Map) { - vitalSignList.add(VitalSignResModel.fromJson(vitalSignJson)); + final vitalSign = VitalSignResModel.fromJson(vitalSignJson); + + // Only add records where BOTH height AND weight are greater than 0 + final hasValidWeight = _isValidValue(vitalSign.weightKg); + final hasValidHeight = _isValidValue(vitalSign.heightCm); + + // Only add if both height and weight are valid (> 0) + if (hasValidWeight && hasValidHeight) { + vitalSignList.add(vitalSign); + } } } } @@ -967,5 +976,22 @@ class HmgServicesRepoImp implements HmgServicesRepo { } } + /// Helper method to check if a value is valid (greater than 0) + bool _isValidValue(dynamic value) { + if (value == null) return false; + + if (value is num) { + return value > 0; + } + + if (value is String) { + if (value.trim().isEmpty) return false; + final parsed = double.tryParse(value); + return parsed != null && parsed > 0; + } + + return false; + } + } diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 7cbdee3..3602c98 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -130,9 +130,13 @@ class LiveChatPage extends StatelessWidget { ).paddingSymmetrical(16.h, 16.h), ).onPress(() { contactUsVM.setSelectedLiveChatProjectIndex(index); - chatURL = - "https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}"; - debugPrint("Chat URL: $chatURL"); + _getChatRequestID( + context, + contactUsVM, + name: appState.getAuthenticatedUser()!.firstName ?? '', + mobileNo: appState.getAuthenticatedUser()!.mobileNumber ?? '', + workGroup: contactUsVM.liveChatProjectsList[index].value ?? '', + ); }), ).paddingSymmetrical(24.h, 0.h), ), @@ -155,8 +159,14 @@ class LiveChatPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.liveChat.tr(context: context), onPressed: () async { - Uri uri = Uri.parse(chatURL); - launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + if (contactUsVM.chatRequestID != null) { + chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${contactUsVM.chatRequestID}"; + debugPrint("Chat URL: $chatURL"); + Uri uri = Uri.parse(chatURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + debugPrint("Chat Request ID is null"); + } }, backgroundColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, borderColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, @@ -173,4 +183,20 @@ class LiveChatPage extends StatelessWidget { }), ); } + + void _getChatRequestID(BuildContext context, ContactUsViewModel contactUsVM, {required String name, required String mobileNo, required String workGroup}) { + contactUsVM.getChatRequestID( + name: name, + mobileNo: mobileNo, + workGroup: workGroup, + onSuccess: (response) { + debugPrint("Chat Request ID received: ${contactUsVM.chatRequestID}"); + chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${contactUsVM.chatRequestID}"; + debugPrint("Chat URL: $chatURL"); + }, + onError: (error) { + debugPrint("Error getting chat request ID: $error"); + }, + ); + } } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 059969c..d183d02 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -85,7 +85,8 @@ class AppRoutes { static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage'; static const String healthTrackerDetailPage = '/healthTrackerDetailPage'; - static Map get routes => { + static Map get routes => + { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), landingScreen: (context) => LandingNavigation(), @@ -116,27 +117,37 @@ class AppRoutes { healthTrackersPage: (context) => HealthTrackersPage(), vitalSign: (context) => VitalSignPage(), addHealthTrackerEntryPage: (context) { - final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; + final args = ModalRoute + .of(context) + ?.settings + .arguments as HealthTrackerTypeEnum?; return AddHealthTrackerEntryPage( trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, ); }, healthTrackerDetailPage: (context) { - final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; + final args = ModalRoute + .of(context) + ?.settings + .arguments as HealthTrackerTypeEnum?; return HealthTrackerDetailPage( trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, ); - - monthlyReports: (context) => ChangeNotifierProvider( - create: (_) => MonthlyReportsViewModel( - monthlyReportsRepo: getIt(), - errorHandlerService: getIt(), + }, + monthlyReports: (context) => + ChangeNotifierProvider( + create: (_) => + MonthlyReportsViewModel( + monthlyReportsRepo: getIt(), + errorHandlerService: getIt(), + ), + child: const MonthlyReportsPage(), ), - child: const MonthlyReportsPage(), - ), + qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), child: const ParkingPage(), - }, - }; + ) + }; + } From 287e2b956289e5d86e46c6d879f6a6e75b92a318 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 14 Jan 2026 10:42:50 +0300 Subject: [PATCH 38/46] fixed QR --- lib/core/api/api_client.dart | 2 +- lib/core/dependencies.dart | 10 +++++----- lib/routes/app_routes.dart | 23 ++++++++++++----------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 039787b..722a45e 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -19,7 +19,7 @@ abstract class ApiClient { Future post( String endPoint, { - required dynamic body, + required Map body, required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, bool isAllowAny, diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index d70191b..5f2444f 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -292,11 +292,11 @@ class AppDependencies { // getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); - // getIt.registerLazySingleton(() => MyInvoicesViewModel( - // myInvoicesRepo: getIt(), - // errorHandlerService: getIt(), - // navServices: getIt(), - // )); + getIt.registerLazySingleton(() => MyInvoicesViewModel( + myInvoicesRepo: getIt(), + errorHandlerService: getIt(), + navServices: getIt(), + )); getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton( () => ActivePrescriptionsViewModel( diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 059969c..632463f 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -126,17 +126,18 @@ class AppRoutes { return HealthTrackerDetailPage( trackerType: args ?? HealthTrackerTypeEnum.bloodSugar, ); - - monthlyReports: (context) => ChangeNotifierProvider( - create: (_) => MonthlyReportsViewModel( - monthlyReportsRepo: getIt(), - errorHandlerService: getIt(), - ), - child: const MonthlyReportsPage(), - ), - qrParking: (context) => ChangeNotifierProvider( - create: (_) => getIt(), - child: const ParkingPage(), }, + + monthlyReports: (context) => ChangeNotifierProvider( + create: (_) => MonthlyReportsViewModel( + monthlyReportsRepo: getIt(), + errorHandlerService: getIt(), + ), + child: const MonthlyReportsPage(), + ), + qrParking: (context) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), + ), }; } From 169215fd7b04f84163e6787a6a256cc02a9971cb Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 14 Jan 2026 11:10:12 +0300 Subject: [PATCH 39/46] translation changes --- assets/langs/ar-SA.json | 96 ++++++++++++++++++- assets/langs/en-US.json | 96 ++++++++++++++++++- lib/extensions/string_extensions.dart | 2 +- lib/generated/locale_keys.g.dart | 94 +++++++++++++++++- .../cmc_selection_review_page.dart | 26 ++--- .../comprehensive_checkup_page.dart | 15 +-- lib/presentation/contact_us/contact_us.dart | 6 +- .../contact_us/feedback_page.dart | 22 ++--- .../contact_us/live_chat_page.dart | 1 - .../contact_us/widgets/find_us_item_card.dart | 4 +- .../covid19test/covid19_landing_page.dart | 6 +- .../covid19test/covid_19_questionnaire.dart | 4 +- .../covid19test/covid_payment_screen.dart | 22 ++--- .../covid19test/covid_review_screen.dart | 10 +- .../e_referral/e-referral_validator.dart | 31 +++--- .../e_referral/e_referral_search_result.dart | 9 +- .../e_referral/new_e_referral.dart | 12 +-- .../e_referral/search_e_referral.dart | 4 +- .../widget/e_referral_other_details.dart | 22 +++-- .../widget/e_referral_patient_info.dart | 20 ++-- .../widget/e_referral_requester_form.dart | 26 ++--- .../call_ambulance/call_ambulance_page.dart | 41 ++++---- .../requesting_services_page.dart | 5 +- .../call_ambulance/tracking_screen.dart | 43 +++++---- .../widgets/pickup_location.dart | 21 ++-- .../widgets/type_selection_widget.dart | 8 +- 26 files changed, 454 insertions(+), 192 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index ee6ac33..da245da 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -694,11 +694,7 @@ "bikini": "بيكيني", "totalMinutes": "إجمالي الدقائق", "feedback": "ملاحظات", - "send": "أرسل", - "status": "الحالة", "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", - "subject": "الموضوع", - "message": "رسالة", "emptySubject": "يرجى إدخال الموضوع", "emptyMessage": "يرجى إدخال الرسالة", "selectAttachment": "اختر المرفق", @@ -1009,5 +1005,95 @@ "orderCancelledSuccessfully": "تم إلغاء الطلب بنجاح", "requestID": "معرف الطلب:", "noCMCOrdersYet": "ليس لديك أي طلبات فحص شامل بعد.", - "cmcOrders": "طلبات الفحص الشامل" + "cmcOrders": "طلبات الفحص الشامل", + "summary": "الملخص", + "selectedService": "الخدمة المحددة", + "requestSubmittedSuccessfully": "تم إرسال طلبك بنجاح.", + "hereIsYourRequestNumber": "هذا هو رقم طلبك #: ", + "pleaseSelectHospitalToContinue": "يرجى اختيار مستشفى للمتابعة", + "confirmSubmitRequest": "هل أنت متأكد أنك تريد إرسال هذا الطلب؟", + "pendingOrderWait": "لديك طلب معلق. يرجى الانتظار حتى تتم معالجته.", + "noServicesAvailable": "لا توجد خدمات متاحة", + "selectAService": "اختر خدمة", + "comprehensiveCheckup": "الفحص الشامل", + "viewNearestHMGLocations": "عرض أقرب مواقع مجموعة الحبيب الطبية", + "provideFeedbackOnServices": "قدم ملاحظاتك على خدماتنا", + "liveChatWithHMG": "خيار الدردشة المباشرة مع مجموعة الحبيب الطبية", + "send": "إرسال", + "status": "الحالة", + "sendingFeedback": "جاري إرسال الملاحظات...", + "selectFeedbackType": "اختر نوع الملاحظات", + "loadingAppointmentsList": "جاري تحميل قائمة المواعيد...", + "noAppointmentsForFeedback": "ليس لديك أي مواعيد لتقديم ملاحظات عنها.", + "selectedAppointment": "الموعد المحدد:", + "subject": "الموضوع", + "enterSubjectHere": "أدخل الموضوع هنا", + "message": "الرسالة", + "enterMessageHere": "أدخل الرسالة هنا", + "filesSelected": "تم تحديد {count} ملف(ات)", + "otherDetails": "تفاصيل أخرى", + "medicalReport": "التقرير الطبي", + "medicalReportNumber": "التقرير الطبي {number}", + "patientIsInsured": "المريض مؤمن عليه", + "insuranceDocument": "وثيقة التأمين", + "selectBranch": "اختر الفرع", + "patientInformation": "معلومات المريض", + "patientLocation": "أين يتواجد المريض", + "identificationNumber": "رقم الهوية", + "enterIdentificationNumber": "أدخل رقم الهوية*", + "patientName": "اسم المريض*", + "referralRequesterInformation": "معلومات مقدم طلب الإحالة", + "enterReferralRequesterName": "أدخل اسم مقدم طلب الإحالة*", + "requesterName": "اسم مقدم الطلب", + "relationship": "العلاقة", + "selectRelation": "اختر العلاقة", + "otherName": "اسم آخر", + "otherNameHint": "اسم آخر*", + "requesterNameRequired": "اسم مقدم طلب الإحالة مطلوب", + "selectRelationshipRequired": "يرجى اختيار العلاقة", + "otherRelationshipNameRequired": "اسم العلاقة الأخرى مطلوب", + "identificationNumberRequired": "رقم الهوية مطلوب", + "patientNameRequired": "اسم المريض مطلوب", + "enterPatientPhoneRequired": "يرجى إدخال رقم هاتف المريض", + "selectPatientCityRequired": "يرجى اختيار مدينة المريض", + "medicalReportRequired": "مطلوب تقرير طبي واحد على الأقل", + "selectBranchRequired": "يرجى اختيار الفرع", + "insuranceDocumentRequired": "وثيقة التأمين مطلوبة للمرضى المؤمن عليهم", + "searchResult": "نتيجة البحث", + "referralNo": "رقم الإحالة {number}", + "eReferral": "الإحالة الإلكترونية", + "referralCreatedSuccessfully": "تم إنشاء إحالتك بنجاح.", + "hereIsYourReferralNumber": "هذا هو رقم الإحالة الخاص بك #: ", + "searchEReferral": "البحث عن إحالة إلكترونية", + "enterRequiredInfoToSearch": "يرجى إدخال المعلومات المطلوبة للبحث عن إحالة إلكترونية", + "selectPickupDirection": "اختر اتجاه الاستلام", + "selectDirection": "اختر الاتجاه", + "toHospital": "إلى المستشفى", + "fromHospital": "من المستشفى", + "selectWay": "اختر الطريقة", + "oneWay": "اتجاه واحد", + "twoWay": "اتجاهين", + "selectPickupDetails": "اختر تفاصيل الاستلام", + "pleaseSelectDetailsOfPickup": " يرجى تحديد تفاصيل الاستلام", + "selectDetails": "اختر التفاصيل", + "work": "العمل", + "pick": "استلام", + "insideTheHome": "داخل المنزل", + "haveAnyAppointment": "هل لديك أي موعد", + "amountPaidAtHospital": "سيتم دفع المبلغ في المستشفى", + "submitRequest": "إرسال الطلب", + "enterPickupLocationManually": "أدخل موقع الاستلام يدوياً", + "enterPickupLocation": "أدخل موقع الاستلام", + "trackingDetails": "تفاصيل التتبع", + "cancelRequest": "إلغاء الطلب", + "shareLocationWhatsapp": "مشاركة موقعك المباشر على واتساب", + "pleaseWaitForCall": "يرجى انتظار المكالمة", + "toHospitalLower": "إلى المستشفى", + "contact": "اتصال", + "failed": "فشل", + "confirmationCall": "مكالمة التأكيد", + "pickupFromHome": "الاستلام من المنزل", + "onTheWayToHospital": " في الطريق إلى المستشفى", + "arrivedAtHospital": "وصل إلى المستشفى", + "orderCancel": "إلغاء الطلب" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6244157..f6500dc 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -689,11 +689,7 @@ "bikini": "Bikini", "totalMinutes": "Total Minutes", "feedback": "Feedback", - "send": "أرسل", - "status": "الحالة", "likeToHear": "We would love to hear the feedback, concerns on healthcare services and eServices experience. Please use the below form", - "subject": "الموضوع", - "message": "رسالة", "emptySubject": "Please enter the subject", "emptyMessage": "Please enter message", "selectAttachment": "Select Attachment", @@ -1005,5 +1001,95 @@ "orderCancelledSuccessfully": "Order has been cancelled successfully", "requestID": "Request ID:", "noCMCOrdersYet": "You don't have any CMC orders yet.", - "cmcOrders": "CMC Orders" + "cmcOrders": "CMC Orders", + "summary": "Summary", + "selectedService": "Selected Service", + "requestSubmittedSuccessfully": "Your request has been successfully submitted.", + "hereIsYourRequestNumber": "Here is your request #: ", + "pleaseSelectHospitalToContinue": "Please select a hospital to continue", + "confirmSubmitRequest": "Are you sure you want to submit this request?", + "pendingOrderWait": "You have a pending order. Please wait for it to be processed.", + "noServicesAvailable": "No services available", + "selectAService": "Select a Service", + "comprehensiveCheckup": "Comprehensive Checkup", + "viewNearestHMGLocations": "View your nearest HMG locations", + "provideFeedbackOnServices": "Provide your feedback on our services", + "liveChatWithHMG": "Live chat option with HMG", + "send": "Send", + "status": "Status", + "sendingFeedback": "Sending Feedback...", + "selectFeedbackType": "Select Feedback Type", + "loadingAppointmentsList": "Loading appointments list...", + "noAppointmentsForFeedback": "You do not have any appointments to submit a feedback.", + "selectedAppointment": "Selected Appointment:", + "subject": "Subject", + "enterSubjectHere": "Enter subject here", + "message": "Message", + "enterMessageHere": "Enter message here", + "filesSelected": "{count} file(s) selected", + "otherDetails": "Other Details", + "medicalReport": "Medical Report", + "medicalReportNumber": "Medical Report {number}", + "patientIsInsured": "Patient is Insured", + "insuranceDocument": "Insurance Document", + "selectBranch": "Select Branch", + "patientInformation": "Patient information", + "patientLocation": "Where the patient located", + "identificationNumber": "Identification Number", + "enterIdentificationNumber": "Enter Identification Number*", + "patientName": "Patient Name*", + "referralRequesterInformation": "Referral requester information", + "enterReferralRequesterName": "Enter Referral Requester Name*", + "requesterName": "Requester Name", + "relationship": "Relationship", + "selectRelation": "Select Relation", + "otherName": "Other Name", + "otherNameHint": "Other Name*", + "requesterNameRequired": "Referral requester name is required", + "selectRelationshipRequired": "Please select a relationship", + "otherRelationshipNameRequired": "Other relationship name is required", + "identificationNumberRequired": "Identification number is required", + "patientNameRequired": "Patient name is required", + "enterPatientPhoneRequired": "Please Enter patient phone number", + "selectPatientCityRequired": "Please select patient city", + "medicalReportRequired": "At least one medical report is required", + "selectBranchRequired": "Please select a branch", + "insuranceDocumentRequired": "Insurance document is required for insured patients", + "searchResult": "Search Result", + "referralNo": "Referral No {number}", + "eReferral": "E Referral", + "referralCreatedSuccessfully": "Your Referral has been created Successfully.", + "hereIsYourReferralNumber": "Here is your Referral #: ", + "searchEReferral": "Search E-Referral", + "enterRequiredInfoToSearch": "Please enter the required information to search for an e-referral", + "selectPickupDirection": "Select Pickup Direction", + "selectDirection": "Select Direction", + "toHospital": "To Hospital", + "fromHospital": "From Hospital", + "selectWay": "Select Way", + "oneWay": "One Way", + "twoWay": "Two Way", + "selectPickupDetails": "Select Pickup Details", + "pleaseSelectDetailsOfPickup": " Please select the details of pickup", + "selectDetails": "Select Details", + "work": "Work", + "pick": "Pick", + "insideTheHome": "Inside the home", + "haveAnyAppointment": "Have any appointment", + "amountPaidAtHospital": "Amount will be paid at the hospital", + "submitRequest": "Submit Request", + "enterPickupLocationManually": "Enter Pickup Location Manually", + "enterPickupLocation": "Enter Pickup Location", + "trackingDetails": "Tracking Details", + "cancelRequest": "Cancel Request", + "shareLocationWhatsapp": "Share Your Live Location on Whatsapp", + "pleaseWaitForCall": "Please wait for the call", + "toHospitalLower": "to hospital", + "contact": "Contact", + "failed": "Failed", + "confirmationCall": "Confirmation Call", + "pickupFromHome": "Pickup Up from Home", + "onTheWayToHospital": " On The Way To Hospital", + "arrivedAtHospital": "Arrived at Hospital", + "orderCancel": "Order Cancel" } \ No newline at end of file diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 309dde1..947bff4 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -15,7 +15,7 @@ extension CapExtension on String { String get allInCaps => toUpperCase(); - String get needTranslation => this; + // String get needTranslation => this; String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f57a48b..fcc0257 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -693,11 +693,7 @@ abstract class LocaleKeys { static const bikini = 'bikini'; static const totalMinutes = 'totalMinutes'; static const feedback = 'feedback'; - static const send = 'send'; - static const status = 'status'; static const likeToHear = 'likeToHear'; - static const subject = 'subject'; - static const message = 'message'; static const emptySubject = 'emptySubject'; static const emptyMessage = 'emptyMessage'; static const selectAttachment = 'selectAttachment'; @@ -1006,5 +1002,95 @@ abstract class LocaleKeys { static const requestID = 'requestID'; static const noCMCOrdersYet = 'noCMCOrdersYet'; static const cmcOrders = 'cmcOrders'; + static const summary = 'summary'; + static const selectedService = 'selectedService'; + static const requestSubmittedSuccessfully = 'requestSubmittedSuccessfully'; + static const hereIsYourRequestNumber = 'hereIsYourRequestNumber'; + static const pleaseSelectHospitalToContinue = 'pleaseSelectHospitalToContinue'; + static const confirmSubmitRequest = 'confirmSubmitRequest'; + static const pendingOrderWait = 'pendingOrderWait'; + static const noServicesAvailable = 'noServicesAvailable'; + static const selectAService = 'selectAService'; + static const comprehensiveCheckup = 'comprehensiveCheckup'; + static const viewNearestHMGLocations = 'viewNearestHMGLocations'; + static const provideFeedbackOnServices = 'provideFeedbackOnServices'; + static const liveChatWithHMG = 'liveChatWithHMG'; + static const send = 'send'; + static const status = 'status'; + static const sendingFeedback = 'sendingFeedback'; + static const selectFeedbackType = 'selectFeedbackType'; + static const loadingAppointmentsList = 'loadingAppointmentsList'; + static const noAppointmentsForFeedback = 'noAppointmentsForFeedback'; + static const selectedAppointment = 'selectedAppointment'; + static const subject = 'subject'; + static const enterSubjectHere = 'enterSubjectHere'; + static const message = 'message'; + static const enterMessageHere = 'enterMessageHere'; + static const filesSelected = 'filesSelected'; + static const otherDetails = 'otherDetails'; + static const medicalReport = 'medicalReport'; + static const medicalReportNumber = 'medicalReportNumber'; + static const patientIsInsured = 'patientIsInsured'; + static const insuranceDocument = 'insuranceDocument'; + static const selectBranch = 'selectBranch'; + static const patientInformation = 'patientInformation'; + static const patientLocation = 'patientLocation'; + static const identificationNumber = 'identificationNumber'; + static const enterIdentificationNumber = 'enterIdentificationNumber'; + static const patientName = 'patientName'; + static const referralRequesterInformation = 'referralRequesterInformation'; + static const enterReferralRequesterName = 'enterReferralRequesterName'; + static const requesterName = 'requesterName'; + static const relationship = 'relationship'; + static const selectRelation = 'selectRelation'; + static const otherName = 'otherName'; + static const otherNameHint = 'otherNameHint'; + static const requesterNameRequired = 'requesterNameRequired'; + static const selectRelationshipRequired = 'selectRelationshipRequired'; + static const otherRelationshipNameRequired = 'otherRelationshipNameRequired'; + static const identificationNumberRequired = 'identificationNumberRequired'; + static const patientNameRequired = 'patientNameRequired'; + static const enterPatientPhoneRequired = 'enterPatientPhoneRequired'; + static const selectPatientCityRequired = 'selectPatientCityRequired'; + static const medicalReportRequired = 'medicalReportRequired'; + static const selectBranchRequired = 'selectBranchRequired'; + static const insuranceDocumentRequired = 'insuranceDocumentRequired'; + static const searchResult = 'searchResult'; + static const referralNo = 'referralNo'; + static const eReferral = 'eReferral'; + static const referralCreatedSuccessfully = 'referralCreatedSuccessfully'; + static const hereIsYourReferralNumber = 'hereIsYourReferralNumber'; + static const searchEReferral = 'searchEReferral'; + static const enterRequiredInfoToSearch = 'enterRequiredInfoToSearch'; + static const selectPickupDirection = 'selectPickupDirection'; + static const selectDirection = 'selectDirection'; + static const toHospital = 'toHospital'; + static const fromHospital = 'fromHospital'; + static const selectWay = 'selectWay'; + static const oneWay = 'oneWay'; + static const twoWay = 'twoWay'; + static const selectPickupDetails = 'selectPickupDetails'; + static const pleaseSelectDetailsOfPickup = 'pleaseSelectDetailsOfPickup'; + static const selectDetails = 'selectDetails'; + static const work = 'work'; + static const pick = 'pick'; + static const insideTheHome = 'insideTheHome'; + static const haveAnyAppointment = 'haveAnyAppointment'; + static const amountPaidAtHospital = 'amountPaidAtHospital'; + static const submitRequest = 'submitRequest'; + static const enterPickupLocationManually = 'enterPickupLocationManually'; + static const enterPickupLocation = 'enterPickupLocation'; + static const trackingDetails = 'trackingDetails'; + static const cancelRequest = 'cancelRequest'; + static const shareLocationWhatsapp = 'shareLocationWhatsapp'; + static const pleaseWaitForCall = 'pleaseWaitForCall'; + static const toHospitalLower = 'toHospitalLower'; + static const contact = 'contact'; + static const failed = 'failed'; + static const confirmationCall = 'confirmationCall'; + static const pickupFromHome = 'pickupFromHome'; + static const onTheWayToHospital = 'onTheWayToHospital'; + static const arrivedAtHospital = 'arrivedAtHospital'; + static const orderCancel = 'orderCancel'; } diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 18b656c..9b78eca 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -55,7 +55,7 @@ class _CmcSelectionReviewPageState extends State { final isArabic = appState.isArabic(); return CollapsingListView( - title: "Summary".needTranslation, + title: LocaleKeys.summary.tr(context: context), bottomChild: _buildBottomButton(), child: SingleChildScrollView( padding: EdgeInsets.all(16.w), @@ -89,7 +89,7 @@ class _CmcSelectionReviewPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Select Hospital".needTranslation, + LocaleKeys.selectHospital.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w700, @@ -132,7 +132,7 @@ class _CmcSelectionReviewPageState extends State { Text( isLocationSelected && selectedHospital != null ? (isArabic ? (selectedHospital.nameN ?? selectedHospital.name ?? '') : (selectedHospital.name ?? '')) - : "Select Hospital".needTranslation, + : LocaleKeys.selectHospital.tr(context: context), style: TextStyle( fontSize: 14.f, fontWeight: isLocationSelected ? FontWeight.w600 : FontWeight.w400, @@ -189,7 +189,7 @@ class _CmcSelectionReviewPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Selected Service".needTranslation.toText14( + LocaleKeys.selectedService.tr(context: context).toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, letterSpacing: -0.4, @@ -226,14 +226,14 @@ class _CmcSelectionReviewPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), // Amount before tax Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( amountBeforeTax.toString().toText16(isBold: true), AppColors.blackColor, @@ -247,7 +247,7 @@ class _CmcSelectionReviewPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( taxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, @@ -261,7 +261,7 @@ class _CmcSelectionReviewPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol( totalAmount.toString().toText24(isBold: true), AppColors.blackColor, @@ -298,7 +298,7 @@ class _CmcSelectionReviewPageState extends State { ], ), child: CustomButton( - text: "Confirm".needTranslation, + text: LocaleKeys.confirm.tr(context: context), onPressed: () { isLocationSelected ? _handleConfirm() : null; }, @@ -339,10 +339,10 @@ class _CmcSelectionReviewPageState extends State { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), Row( children: [ - "Here is your request #: ".needTranslation.toText14( + LocaleKeys.hereIsYourRequestNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -383,7 +383,7 @@ class _CmcSelectionReviewPageState extends State { if (selectedHospital == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text("Please select a hospital to continue".needTranslation), + content: Text(LocaleKeys.pleaseSelectHospitalToContinue.tr(context: context)), backgroundColor: AppColors.errorColor, ), ); @@ -395,7 +395,7 @@ class _CmcSelectionReviewPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to submit this request?".needTranslation, + loadingText: LocaleKeys.confirmSubmitRequest.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart index 8b9ad89..da926cd 100644 --- a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_order_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_selection_review_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart'; @@ -128,7 +129,7 @@ class _ComprehensiveCheckupPageState extends State { // Request ID Row( children: [ - "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, weight: FontWeight.w500), SizedBox(width: 4.w), "${order.iD ?? '-'}".toText16(isBold: true), ], @@ -179,7 +180,7 @@ class _ComprehensiveCheckupPageState extends State { ), SizedBox(width: 8.w), Expanded( - child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + child: LocaleKeys.pendingOrderWait.tr(context: context).toText12( color: AppColors.infoBannerTextColor, fontWeight: FontWeight.w500, ), @@ -193,7 +194,7 @@ class _ComprehensiveCheckupPageState extends State { children: [ Expanded( child: CustomButton( - text: "Cancel Order".needTranslation, + text: LocaleKeys.cancelOrder.tr(context: context), onPressed: () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -219,7 +220,7 @@ class _ComprehensiveCheckupPageState extends State { child: Padding( padding: EdgeInsets.all(24.h), child: Text( - 'No services available'.needTranslation, + LocaleKeys.noServicesAvailable.tr(context: context), style: TextStyle( fontSize: 16.h, color: AppColors.greyTextColor, @@ -234,7 +235,7 @@ class _ComprehensiveCheckupPageState extends State { children: [ SizedBox(height: 16.h), Text( - 'Select a Service'.needTranslation, + LocaleKeys.selectAService.tr(context: context), style: TextStyle( fontSize: 20.h, fontWeight: FontWeight.w700, @@ -357,7 +358,7 @@ class _ComprehensiveCheckupPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Comprehensive Checkup".needTranslation, + title: LocaleKeys.comprehensiveCheckup.tr(context: context), history: () => Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)), bottomChild: Consumer( builder: (context, hmgServicesViewModel, child) { @@ -375,7 +376,7 @@ class _ComprehensiveCheckupPageState extends State { padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), child: CustomButton( borderWidth: 0, - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: _proceedWithSelectedService, textColor: AppColors.whiteColor, borderRadius: 12.r, diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index d7ea9c5..40d42d5 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -36,7 +36,7 @@ class ContactUs extends StatelessWidget { checkInOptionCard( AppAssets.checkin_location_icon, LocaleKeys.findUs.tr(), - "View your nearest HMG locations".needTranslation, + LocaleKeys.viewNearestHMGLocations.tr(), ).onPress(() { locationUtils.getCurrentLocation(onSuccess: (value) { contactUsViewModel.initContactUsViewModel(); @@ -52,7 +52,7 @@ class ContactUs extends StatelessWidget { checkInOptionCard( AppAssets.checkin_location_icon, LocaleKeys.feedback.tr(), - "Provide your feedback on our services".needTranslation, + LocaleKeys.provideFeedbackOnServices.tr(), ).onPress(() { contactUsViewModel.setSelectedFeedbackType( FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), @@ -68,7 +68,7 @@ class ContactUs extends StatelessWidget { checkInOptionCard( AppAssets.checkin_location_icon, LocaleKeys.liveChat.tr(), - "Live chat option with HMG".needTranslation, + LocaleKeys.liveChatWithHMG.tr(), ).onPress(() { locationUtils.getCurrentLocation(onSuccess: (value) { contactUsViewModel.getLiveChatProjectsList(); diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index 078b3a1..62ae1d3 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -55,8 +55,8 @@ class FeedbackPage extends StatelessWidget { activeTextColor: AppColors.primaryRedColor, activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), tabs: [ - CustomTabBarModel(null, "Send".needTranslation), - CustomTabBarModel(null, "Status".needTranslation), + CustomTabBarModel(null, LocaleKeys.send.tr(context: context)), + CustomTabBarModel(null, LocaleKeys.status.tr(context: context)), ], onTabChange: (index) { contactUsViewModel.setIsSendFeedbackTabSelected(index == 0); @@ -93,7 +93,7 @@ class FeedbackPage extends StatelessWidget { ); return; } - LoaderBottomSheet.showLoader(loadingText: "Sending Feedback...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingFeedback.tr(context: context)); contactUsViewModel.insertCOCItem( subject: subjectTextController.text, message: messageTextController.text, @@ -172,7 +172,7 @@ class FeedbackPage extends StatelessWidget { ], ).onPress(() { showCommonBottomSheetWithoutHeight(context, - title: "Select Feedback Type".needTranslation, + title: LocaleKeys.selectFeedbackType.tr(context: context), child: Container( width: double.infinity, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), @@ -207,7 +207,7 @@ class FeedbackPage extends StatelessWidget { Navigator.pop(context); contactUsViewModel.setSelectedFeedbackType(newValue!); if (contactUsViewModel.selectedFeedbackType.id == 1) { - LoaderBottomSheet.showLoader(loadingText: "Loading appointments list...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingAppointmentsList.tr(context: context)); await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { LoaderBottomSheet.hideLoader(); bool? value = await Navigator.of(context).push( @@ -224,7 +224,7 @@ class FeedbackPage extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "You do not have any appointments to submit a feedback.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.noAppointmentsForFeedback.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -248,7 +248,7 @@ class FeedbackPage extends StatelessWidget { ), if (contactUsViewModel.patientFeedbackSelectedAppointment != null) ...[ SizedBox(height: 16.h), - "Selected Appointment:".needTranslation.toText16(isBold: true), + LocaleKeys.selectedAppointment.tr(context: context).toText16(isBold: true), SizedBox(height: 8.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -295,8 +295,8 @@ class FeedbackPage extends StatelessWidget { ], SizedBox(height: 16.h), TextInputWidget( - labelText: "Subject".needTranslation, - hintText: "Enter subject here".needTranslation, + labelText: LocaleKeys.subject.tr(context: context), + hintText: LocaleKeys.enterSubjectHere.tr(context: context), controller: subjectTextController, isEnable: true, prefix: null, @@ -310,8 +310,8 @@ class FeedbackPage extends StatelessWidget { ), SizedBox(height: 16.h), TextInputWidget( - labelText: "Message".needTranslation, - hintText: "Enter message here".needTranslation, + labelText: LocaleKeys.message.tr(context: context), + hintText: LocaleKeys.enterMessageHere.tr(context: context), controller: messageTextController, isEnable: true, prefix: null, diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 7cbdee3..ac511f3 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -114,7 +114,6 @@ class LiveChatPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM") - .needTranslation .toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor), Transform.flip( flipX: getIt.get().isArabic(), diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart index 96375c0..6e295e0 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -68,7 +68,7 @@ class FindUsItemCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km".needTranslation, + labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km", icon: AppAssets.location_red, iconColor: AppColors.primaryRedColor, backgroundColor: AppColors.secondaryLightRedColor, @@ -77,7 +77,7 @@ class FindUsItemCard extends StatelessWidget { Row( children: [ AppCustomChipWidget( - labelText: "Get Directions".needTranslation, + labelText: LocaleKeys.getDirections.tr(), icon: AppAssets.directions_icon, iconColor: AppColors.whiteColor, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), diff --git a/lib/presentation/covid19test/covid19_landing_page.dart b/lib/presentation/covid19test/covid19_landing_page.dart index 62bd651..a241da6 100644 --- a/lib/presentation/covid19test/covid19_landing_page.dart +++ b/lib/presentation/covid19test/covid19_landing_page.dart @@ -94,7 +94,7 @@ class _Covid19LandingPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomButton( - text: "SelectLocation".needTranslation, + text: "Select Location", onPressed: () { _showBranchBottomSheet(context); }, @@ -124,7 +124,7 @@ class _Covid19LandingPageState extends State { showCommonBottomSheet( context, - title: "Select Branch".needTranslation, + title: "Select Branch", height: ResponsiveExtension.screenHeight * 0.651, child: StatefulBuilder( builder: (context, setBottomSheetState) { @@ -240,7 +240,7 @@ class _Covid19LandingPageState extends State { child: SafeArea( top: false, child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: (){ Navigator.of(context) diff --git a/lib/presentation/covid19test/covid_19_questionnaire.dart b/lib/presentation/covid19test/covid_19_questionnaire.dart index 8608d80..f7b1f4c 100644 --- a/lib/presentation/covid19test/covid_19_questionnaire.dart +++ b/lib/presentation/covid19test/covid_19_questionnaire.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/covid_questionnare_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/covid19test/covid_review_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -61,7 +63,7 @@ class _Covid19QuestionnaireState extends State { topRight: Radius.circular(24.r), ), ),child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () { moveToNextPage(context); }, diff --git a/lib/presentation/covid19test/covid_payment_screen.dart b/lib/presentation/covid19test/covid_payment_screen.dart index 42e7736..e395a42 100644 --- a/lib/presentation/covid19test/covid_payment_screen.dart +++ b/lib/presentation/covid19test/covid_payment_screen.dart @@ -89,7 +89,7 @@ class _CovidPaymentScreenState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: widget.title.needTranslation, + title: widget.title, bottomChild: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -100,19 +100,19 @@ class _CovidPaymentScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(( (widget.amount - widget.taxAmount).toString()).toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), // Show VAT amount passed from review screen Utils.getPaymentAmountWithSymbol((widget.taxAmount.toString()).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), ], @@ -121,7 +121,7 @@ class _CovidPaymentScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.amount.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), @@ -212,7 +212,7 @@ class _CovidPaymentScreenState extends State { children: [ Image.asset(AppAssets.mada, width: 72.h, height: 25.h), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true), + LocaleKeys.mada.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -257,7 +257,7 @@ class _CovidPaymentScreenState extends State { ], ), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -312,7 +312,7 @@ class _CovidPaymentScreenState extends State { }, ), SizedBox(height: 16.h), - "Tamara".needTranslation.toText16(isBold: true), + LocaleKeys.tamara.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -387,7 +387,7 @@ class _CovidPaymentScreenState extends State { } Future checkPaymentStatus() async { - LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); try { await payfortViewModel.checkPaymentStatus(transactionID: transID, onSuccess: (apiResponse) async { // treat any successful responseMessage as success; otherwise show generic error @@ -396,7 +396,7 @@ class _CovidPaymentScreenState extends State { if (success) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Payment successful".needTranslation), + child: Utils.getSuccessWidget(loadingText: "Payment successful"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -404,7 +404,7 @@ class _CovidPaymentScreenState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/covid19test/covid_review_screen.dart b/lib/presentation/covid19test/covid_review_screen.dart index 7ccbb02..3440129 100644 --- a/lib/presentation/covid19test/covid_review_screen.dart +++ b/lib/presentation/covid19test/covid_review_screen.dart @@ -75,7 +75,7 @@ class _CovidReviewScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText18(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), Utils.getPaymentAmountWithSymbol( (info.patientShareField ?? 0).toString().toText16(isBold: true), AppColors.blackColor, @@ -87,7 +87,7 @@ class _CovidReviewScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Tax Amount".needTranslation.toText14(isBold: true), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( (info.patientTaxAmountField ?? 0).toString().toText16(isBold: true), AppColors.blackColor, @@ -208,13 +208,13 @@ class _CovidReviewScreenState extends State { Expanded( child: CustomButton( height: 56.h, - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () async { // Validate selection and payment info if (_selectedProcedure == null) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Please select a procedure".needTranslation), + child: Utils.getErrorWidget(loadingText: "Please select a procedure"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -236,7 +236,7 @@ class _CovidReviewScreenState extends State { if (hmgServicesViewModel.covidPaymentInfo == null) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment information not available".needTranslation), + child: Utils.getErrorWidget(loadingText: "Payment information not available"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/e_referral/e-referral_validator.dart b/lib/presentation/e_referral/e-referral_validator.dart index e0a8006..b662122 100644 --- a/lib/presentation/e_referral/e-referral_validator.dart +++ b/lib/presentation/e_referral/e-referral_validator.dart @@ -1,64 +1,65 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; class ReferralValidator { - static FormValidationErrors validateStep1(ReferralFormData formData) { + static FormValidationErrors validateStep1(ReferralFormData formData, BuildContext context) { final errors = FormValidationErrors(); if (formData.requesterName.trim().isEmpty) { - errors.requesterName = 'Referral requester name is required'.needTranslation; + errors.requesterName = LocaleKeys.requesterNameRequired.tr(context: context); } - - if (formData.relationship == null) { - errors.relationship = 'Please select a relationship'.needTranslation; + errors.relationship = LocaleKeys.selectRelationshipRequired.tr(context: context); } if (formData.relationship != null && formData.relationship?.iD == 5 && formData.otherRelationshipName.trim().isEmpty) { - errors.otherRelationshipName = 'Other relationship name is required'.needTranslation; + errors.otherRelationshipName = LocaleKeys.otherRelationshipNameRequired.tr(context: context); } return errors; } - static FormValidationErrors validateStep2(ReferralFormData formData) { + static FormValidationErrors validateStep2(ReferralFormData formData, BuildContext context) { final errors = FormValidationErrors(); if (formData.patientIdentification.trim().isEmpty) { - errors.patientIdentification = 'Identification number is required'.needTranslation; + errors.patientIdentification = LocaleKeys.identificationNumberRequired.tr(context: context); } if (formData.patientName.trim().isEmpty) { - errors.patientName = 'Patient name is required'.needTranslation; + errors.patientName = LocaleKeys.patientNameRequired.tr(context: context); } if (formData.patientPhone == null) { - errors.patientPhone = 'Please Enter patient phone number'.needTranslation; + errors.patientPhone = LocaleKeys.enterPatientPhoneRequired.tr(context: context); } if (formData.patientCity == null) { - errors.patientCity = 'Please select patient city'.needTranslation; + errors.patientCity = LocaleKeys.selectPatientCityRequired.tr(context: context); } return errors; } - static FormValidationErrors validateStep3(ReferralFormData formData) { + static FormValidationErrors validateStep3(ReferralFormData formData, BuildContext context) { final errors = FormValidationErrors(); if (formData.medicalReportImages.isEmpty) { - errors.medicalReport = 'At least one medical report is required'.needTranslation; + errors.medicalReport = LocaleKeys.medicalReportRequired.tr(context: context); } if (formData.branch == null) { - errors.branch = 'Please select a branch'.needTranslation; + errors.branch = LocaleKeys.selectBranchRequired.tr(context: context); } if (formData.isPatientInsured && formData.insuredPatientImages.isEmpty) { - errors.insuredDocument = 'Insurance document is required for insured patients'.needTranslation; + errors.insuredDocument = LocaleKeys.insuranceDocumentRequired.tr(context: context); } return errors; diff --git a/lib/presentation/e_referral/e_referral_search_result.dart b/lib/presentation/e_referral/e_referral_search_result.dart index 8702d5a..6a4b831 100644 --- a/lib/presentation/e_referral/e_referral_search_result.dart +++ b/lib/presentation/e_referral/e_referral_search_result.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; @@ -5,6 +6,7 @@ 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/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_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:provider/provider.dart'; @@ -31,7 +33,7 @@ class _SearchResultPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Search Result".needTranslation, + title: LocaleKeys.searchResult.tr(context: context), child: Column( children: [ // List of referrals @@ -61,10 +63,7 @@ class _SearchResultPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - - 'Referral No ${referral.referralNumber}'.needTranslation.toText18(isBold: true, color: AppColors.textColor), - - + LocaleKeys.referralNo.tr(context: context, namedArgs: {'number': '${referral.referralNumber}'}).toText18(isBold: true, color: AppColors.textColor), Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( diff --git a/lib/presentation/e_referral/new_e_referral.dart b/lib/presentation/e_referral/new_e_referral.dart index 3083de1..0288173 100644 --- a/lib/presentation/e_referral/new_e_referral.dart +++ b/lib/presentation/e_referral/new_e_referral.dart @@ -81,13 +81,13 @@ class _NewReferralPageState extends State { switch (_currentStep) { case 0: - stepErrors = ReferralValidator.validateStep1(_formManager.formData); + stepErrors = ReferralValidator.validateStep1(_formManager.formData, context); break; case 1: - stepErrors = ReferralValidator.validateStep2(_formManager.formData); + stepErrors = ReferralValidator.validateStep2(_formManager.formData, context); break; case 2: - stepErrors = ReferralValidator.validateStep3(_formManager.formData); + stepErrors = ReferralValidator.validateStep3(_formManager.formData, context); break; default: stepErrors = FormValidationErrors(); @@ -159,7 +159,7 @@ class _NewReferralPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "E Referral".needTranslation, + title: LocaleKeys.eReferral.tr(context: context), isClose: false, search: () async { await Navigator.of(context).push( @@ -222,10 +222,10 @@ class _NewReferralPageState extends State { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Your Referral has been created Successfully.".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.referralCreatedSuccessfully.tr(context: context)), Row( children: [ - "Here is your Referral #: ".needTranslation.toText14( + LocaleKeys.hereIsYourReferralNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), diff --git a/lib/presentation/e_referral/search_e_referral.dart b/lib/presentation/e_referral/search_e_referral.dart index 1a243da..b9bf592 100644 --- a/lib/presentation/e_referral/search_e_referral.dart +++ b/lib/presentation/e_referral/search_e_referral.dart @@ -80,7 +80,7 @@ class _SearchEReferralPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Search E-Referral".needTranslation, + title: LocaleKeys.searchEReferral.tr(context: context), isClose: true, bottomChild: Container( color: Colors.white, @@ -111,7 +111,7 @@ class _SearchEReferralPageState extends State { padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [SizedBox(height: 8), 'Please enter the required information to search for an e-referral'.needTranslation.toText12()], + children: [SizedBox(height: 8), LocaleKeys.enterRequiredInfoToSearch.tr(context: context).toText12()], ), ); } diff --git a/lib/presentation/e_referral/widget/e_referral_other_details.dart b/lib/presentation/e_referral/widget/e_referral_other_details.dart index 6efd063..da955f8 100644 --- a/lib/presentation/e_referral/widget/e_referral_other_details.dart +++ b/lib/presentation/e_referral/widget/e_referral_other_details.dart @@ -1,10 +1,12 @@ import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/validation_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/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -46,14 +48,14 @@ class _OtherDetailsStepState extends State { void _updateMedicalReportText() { final hasMedicalReports = _formManager.formData.medicalReportImages.isNotEmpty; _medicalReportController.text = hasMedicalReports - ? '${_formManager.formData.medicalReportImages.length} file(s) selected'.needTranslation + ? '${_formManager.formData.medicalReportImages.length} file(s) selected' : ''; } void _updateInsuranceText() { final hasInsuranceDocs = _formManager.formData.insuredPatientImages.isNotEmpty; _insuranceController.text = hasInsuranceDocs - ? '${_formManager.formData.insuredPatientImages.length} file(s) selected'.needTranslation + ? '${_formManager.formData.insuredPatientImages.length} file(s) selected' : ''; } @@ -68,7 +70,7 @@ class _OtherDetailsStepState extends State { physics: const BouncingScrollPhysics(), children: [ const SizedBox(height: 12), - _buildSectionTitle('Other Details'.needTranslation), + _buildSectionTitle(LocaleKeys.otherDetails.tr(context: context)), const SizedBox(height: 12), _buildMedicalReportField(formManager), _buildBranchField(context, formManager), @@ -96,8 +98,8 @@ class _OtherDetailsStepState extends State { child: TextInputWidget( controller: _medicalReportController, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Medical Report'.needTranslation, - labelText: 'Select Attachment'.needTranslation, + hintText: LocaleKeys.medicalReport.tr(context: context), + labelText: LocaleKeys.selectAttachment.tr(context: context), suffix: const Icon(Icons.attachment), isReadOnly: true, errorMessage: formManager.errors.medicalReport, @@ -121,7 +123,7 @@ class _OtherDetailsStepState extends State { children: formManager.formData.medicalReportImages.asMap().entries.map((entry) { final index = entry.key; return Chip( - label: Text('Medical Report ${index + 1}'.needTranslation), + label: Text(LocaleKeys.medicalReportNumber.tr(context: context, namedArgs: {'number': '${index + 1}'})), deleteIcon: const Icon(Icons.close, size: 16), onDeleted: () { _removeMedicalReport(index, formManager); @@ -172,7 +174,7 @@ class _OtherDetailsStepState extends State { Padding( padding: EdgeInsets.all(5.0), child: - "Patient is Insured".needTranslation.toText14( + LocaleKeys.patientIsInsured.tr(context: context).toText14( color: Colors.black, weight: FontWeight.w600, ), @@ -193,8 +195,8 @@ class _OtherDetailsStepState extends State { child: TextInputWidget( controller: _insuranceController, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Insurance Document'.needTranslation, - labelText: 'Select Attachment'.needTranslation, + hintText: LocaleKeys.insuranceDocument.tr(context: context), + labelText: LocaleKeys.selectAttachment.tr(context: context), suffix: const Icon(Icons.attachment), isReadOnly: true, errorMessage: formManager.errors.insuredDocument, @@ -235,7 +237,7 @@ class _OtherDetailsStepState extends State { showCommonBottomSheetWithoutHeight( context, - title: "Select Branch".needTranslation, + title: LocaleKeys.selectBranch.tr(context: context), child: Consumer( builder: (context, habibWalletVM, child) { final hospitals = habibWalletVM.advancePaymentHospitals; diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart index 469755c..0cc5c7e 100644 --- a/lib/presentation/e_referral/widget/e_referral_patient_info.dart +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/validation_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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -65,14 +67,14 @@ class PatientInformationStepState extends State { physics: const BouncingScrollPhysics(), children: [ const SizedBox(height: 12), - _buildSectionTitle('Patient information'.needTranslation), + _buildSectionTitle(LocaleKeys.patientInfo.tr(context: context)), const SizedBox(height: 12), _buildIdentificationField(formManager), _buildPatientNameField(formManager), // _buildPatientCountryField(context, formManager), _buildPatientPhoneField(formManager), const SizedBox(height: 20), - _buildSectionTitle('Where the patient located'.needTranslation), + _buildSectionTitle(LocaleKeys.patientLocation.tr(context: context)), _buildPatientCityField(context, formManager), ], ), @@ -94,8 +96,8 @@ class PatientInformationStepState extends State { child: TextInputWidget( controller: _identificationController, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Enter Identification Number*'.needTranslation, - labelText: 'Identification Number'.needTranslation, + hintText: LocaleKeys.enterIdentificationNumber.tr(context: context), + labelText: LocaleKeys.identificationNumber.tr(context: context), errorMessage: formManager.errors.patientIdentification, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientIdentification), onChange: (value) { @@ -114,8 +116,8 @@ class PatientInformationStepState extends State { child: TextInputWidget( controller: _nameController, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Patient Name*'.needTranslation, - labelText: 'Name'.needTranslation, + hintText: LocaleKeys.patientName.tr(context: context), + labelText: LocaleKeys.name.tr(context: context), keyboardType: TextInputType.text, errorMessage: formManager.errors.patientName, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientName), @@ -133,7 +135,7 @@ class PatientInformationStepState extends State { return Focus( focusNode: _phoneFocusNode, child: TextInputWidget( - labelText: 'Phone Number'.needTranslation, + labelText: LocaleKeys.phoneNumber.tr(context: context), hintText: "5xxxxxxxx", controller: _phoneController, padding: const EdgeInsets.all(8), @@ -159,7 +161,7 @@ class PatientInformationStepState extends State { Widget _buildPatientCityField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( labelText: 'City', - hintText: formManager.formData.patientCity?.description ?? "Select City".needTranslation, + hintText: formManager.formData.patientCity?.description ?? LocaleKeys.selectCity.tr(context: context), isEnable: false, hasSelectionCustomIcon: true, labelColor: Colors.black, @@ -179,7 +181,7 @@ class PatientInformationStepState extends State { showCommonBottomSheetWithoutHeight( context, - title: "Select City".needTranslation, + title: LocaleKeys.selectCity.tr(context: context), child: Consumer( builder: (context, hmgServicesVM, child) { final cities = hmgServicesVM.getAllCitiesList; diff --git a/lib/presentation/e_referral/widget/e_referral_requester_form.dart b/lib/presentation/e_referral/widget/e_referral_requester_form.dart index cc981a9..d2b75cc 100644 --- a/lib/presentation/e_referral/widget/e_referral_requester_form.dart +++ b/lib/presentation/e_referral/widget/e_referral_requester_form.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/validation_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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -67,8 +69,8 @@ class RequesterFormStepState extends State { physics: const BouncingScrollPhysics(), children: [ // const SizedBox(height: 12), - _buildSectionTitle('Referral requester information'.needTranslation), - const SizedBox(height: 12), + _buildSectionTitle(LocaleKeys.referralRequesterInformation.tr(context: context)), + const SizedBox(height: 12), _buildNameField(formManager), // _buildPhoneField(formManager), _buildRelationshipField(context, formManager), @@ -93,8 +95,8 @@ class RequesterFormStepState extends State { child: TextInputWidget( controller: _nameController, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Enter Referral Requester Name*'.needTranslation, - labelText: 'Requester Name'.needTranslation, + hintText: LocaleKeys.enterReferralRequesterName.tr(context: context), + labelText: LocaleKeys.requesterName.tr(context: context), keyboardType: TextInputType.text, errorMessage: formManager.errors.requesterName, isAllowLeadingIcon: true, @@ -109,10 +111,10 @@ class RequesterFormStepState extends State { } Widget _buildRelationshipField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( - labelText: "Relationship".needTranslation, - hintText: formManager.formData.relationship?.textEn ?? "Select Relation".needTranslation, + labelText: LocaleKeys.relationship.tr(context: context), + hintText: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context), isEnable: false, - selectedValue: formManager.formData.relationship?.textEn ?? "Select Relation".needTranslation, + selectedValue: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context), errorMessage: formManager.errors.relationship, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.relationship), hasSelectionCustomIcon: false, @@ -132,8 +134,8 @@ class RequesterFormStepState extends State { controller: _otherNameController, keyboardType: TextInputType.text, padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Other Name*'.needTranslation, - labelText: 'Other Name'.needTranslation, + hintText: LocaleKeys.otherNameHint.tr(context: context), + labelText: LocaleKeys.otherName.tr(context: context), errorMessage: formManager.errors.otherRelationshipName, onChange: (value) { formManager.updateOtherRelationshipName(value ?? ''); @@ -152,7 +154,7 @@ class RequesterFormStepState extends State { showCommonBottomSheetWithoutHeight( context, - title: "Select Relation".needTranslation, + title: LocaleKeys.selectRelation.tr(context: context), child: Consumer( builder: (context, hmgServicesVM, child) { if (hmgServicesVM.relationTypes.isEmpty) { @@ -164,8 +166,8 @@ class RequesterFormStepState extends State { ); } - return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, customBorder: BorderRadius.all(Radius.circular(24.h)) , diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index 043c231..e86104b 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -156,18 +156,18 @@ class CallAmbulancePage extends StatelessWidget { Column( spacing: 4.h, children: [ - "Select Pickup Details".needTranslation.toText21( + LocaleKeys.selectPickupDetails.tr(context: context).toText21( weight: FontWeight.w600, color: AppColors.textColor, ), - " Please select the details of pickup".needTranslation.toText12( + LocaleKeys.pleaseSelectDetailsOfPickup.tr(context: context).toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) ], ), CustomButton( - text: "Select Details".needTranslation, + text: LocaleKeys.selectDetails.tr(context: context), onPressed: () { context.read().updateBottomSheetState(BottomSheetType.EXPANDED); }) @@ -256,7 +256,7 @@ class CallAmbulancePage extends StatelessWidget { height: 40.h, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, - text: "Add new address".needTranslation, + text: LocaleKeys.addNewAddress.tr(context: context), textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, onPressed: () {}, @@ -265,7 +265,7 @@ class CallAmbulancePage extends StatelessWidget { return AddressItem( isSelected: index == 0, address: "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", - title: index == 0 ? "Home".needTranslation : "Work".needTranslation, + title: index == 0 ? LocaleKeys.home.tr(context: context) : LocaleKeys.work.tr(context: context), onTap: () {}, ); } @@ -309,8 +309,8 @@ class CallAmbulancePage extends StatelessWidget { Row( children: [ hospitalAndPickUpItemContent( - title: "Pick".needTranslation, - subTitle: "Inside the home".needTranslation, + title: LocaleKeys.pick.tr(context: context), + subTitle: LocaleKeys.insideTheHome.tr(context: context), leadingIcon: AppAssets.pickup_bed, ), CustomSwitch( @@ -325,8 +325,8 @@ class CallAmbulancePage extends StatelessWidget { Row( children: [ hospitalAndPickUpItemContent( - title: 'Appointment', - subTitle: "Have any appointment".needTranslation, + title: LocaleKeys.appointment.tr(context: context), + subTitle: LocaleKeys.haveAnyAppointment.tr(context: context), leadingIcon: AppAssets.appointment_calendar_icon, ), CustomSwitch( @@ -426,7 +426,7 @@ class CallAmbulancePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Total amount to pay".needTranslation.toText18( + LocaleKeys.totalAmountToPay.tr(context: context).toText18( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -436,7 +436,7 @@ class CallAmbulancePage extends StatelessWidget { SizedBox( width: 4.h, ), - "Amount will be paid at the hospital".needTranslation.toText12( + LocaleKeys.amountPaidAtHospital.tr(context: context).toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), @@ -455,7 +455,7 @@ class CallAmbulancePage extends StatelessWidget { ], ), CustomButton( - text: "Submit Request".needTranslation, + text: LocaleKeys.submitRequest.tr(context: context), onPressed: () { LocationViewModel locationViewModel = context.read(); GeocodeResponse? response = locationViewModel.geocodeResponse; @@ -530,8 +530,8 @@ class CallAmbulancePage extends StatelessWidget { return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, + labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), + hintText: LocaleKeys.enterPickupLocation.tr(context: context), controller: TextEditingController( text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), @@ -563,7 +563,7 @@ class CallAmbulancePage extends StatelessWidget { openLocationInputBottomSheet(BuildContext context) { context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( - title: "".needTranslation, + title: "", context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, @@ -583,25 +583,22 @@ class CallAmbulancePage extends StatelessWidget { child: Row( children: [ hospitalAndPickUpItemContent( - title: "Select Hospital".needTranslation, - subTitle: context.read().getSelectedHospitalName() ?? "Select Hospital".needTranslation, + title: LocaleKeys.selectHospital.tr(context: context), + subTitle: context.read().getSelectedHospitalName() ?? LocaleKeys.selectHospital.tr(context: context), leadingIcon: AppAssets.hospital, ), Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h) ], ).onPress(() { - print("the item is clicked"); showHospitalBottomSheet(context); }).paddingSymmetrical( - 10.w, - 12.h, - ), + 10.w, 12.h), ); } void openAppointmentList(BuildContext context) { showCommonBottomSheetWithoutHeight( - title: "Select Appointment".needTranslation, + title: LocaleKeys.selectAppointment.tr(context: context), context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .5, diff --git a/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart index 396e509..bc6d9b0 100644 --- a/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:lottie/lottie.dart'; @@ -27,8 +29,7 @@ class RequestingServicesPage extends StatelessWidget { .center, Positioned( bottom: 1, - child: "Submitting your request. \nPlease wait for a moment" - .needTranslation + child: LocaleKeys.submitRequest.tr(context: context) .toText16(color: AppColors.textColor, weight: FontWeight.w500) .paddingOnly(bottom: 100.h, left: 100.h, right: 100.h)) ], diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index e5dc0b9..9a49dd0 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -1,5 +1,6 @@ import 'dart:io'; +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'; @@ -9,6 +10,7 @@ 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/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.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/buttons/custom_button.dart'; @@ -60,14 +62,14 @@ class TrackingScreen extends StatelessWidget { iconSize: 18.w, backgroundColor: AppColors.bgGreenColor, borderColor: Colors.transparent, - text: "Close".needTranslation, + text: LocaleKeys.close.tr(context: context), textColor: AppColors.whiteColor, onPressed: () {}, ).paddingOnly(left: 16.h, right: 16.h), ), ), body: CollapsingListView( - title: "Tracking Details".needTranslation, + title: LocaleKeys.trackingDetails.tr(context: context), child: SingleChildScrollView( child: Column( children: [ @@ -136,7 +138,7 @@ class TrackingScreen extends StatelessWidget { height: 16, ), CustomButton( - text: "Cancel Request".needTranslation, + text: LocaleKeys.cancelRequest.tr(context: context), onPressed: () async { openCancelOrderBottomSheet(context); }, @@ -162,7 +164,7 @@ class TrackingScreen extends StatelessWidget { iconSize: 18.w, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, - text: "Share Your Live Location on Whatsapp".needTranslation, + text: LocaleKeys.shareLocationWhatsapp.tr(context: context), fontSize: 12.f, textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, @@ -280,7 +282,7 @@ class TrackingScreen extends StatelessWidget { return RichText( text: TextSpan(children: [ TextSpan( - text: "Please wait for the call".needTranslation, + text: LocaleKeys.pleaseWaitForCall.tr(), style: TextStyle( fontSize: 21.f, fontWeight: FontWeight.w600, @@ -288,7 +290,7 @@ class TrackingScreen extends StatelessWidget { ), ), TextSpan( - text: "...".needTranslation, + text: "...", style: TextStyle( fontSize: 21.f, fontWeight: FontWeight.w600, @@ -301,7 +303,7 @@ class TrackingScreen extends StatelessWidget { return RichText( text: TextSpan(children: [ TextSpan( - text: "15:30".needTranslation, + text: "15:30", style: TextStyle( fontSize: 21.f, fontWeight: FontWeight.w600, @@ -309,7 +311,7 @@ class TrackingScreen extends StatelessWidget { ), ), TextSpan( - text: " mins ".needTranslation, + text: LocaleKeys.mins.tr(), style: TextStyle( fontSize: 21.f, fontWeight: FontWeight.w600, @@ -317,7 +319,7 @@ class TrackingScreen extends StatelessWidget { ), ), TextSpan( - text: "to hospital".needTranslation, + text: LocaleKeys.toHospitalLower.tr(), style: TextStyle( fontSize: 21.f, fontWeight: FontWeight.w600, @@ -328,7 +330,7 @@ class TrackingScreen extends StatelessWidget { ); case OrderTrackingState.ended: - return "Arrived".needTranslation.toText21(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.arrived.tr().toText21(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.failed: case OrderTrackingState.cancel: return SizedBox.shrink(); @@ -386,8 +388,8 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Contact".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), - "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500).onPress((){ + LocaleKeys.contact.tr().toText14(color: AppColors.textColor, weight: FontWeight.w600), + "0115259555".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500).onPress((){ launchUrl( Uri.parse("tel://0115259555"), ); @@ -429,23 +431,24 @@ class TrackingScreen extends StatelessWidget { // } getTitle(OrderTrackingState? state) { - if(state == null) - return "Failed".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + if(state == null) { + return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + } switch (state) { case OrderTrackingState.waitingForCall: - return "Confirmation Call".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.confirmationCall.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.dispactched: - return "Pickup Up from Home".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.pickupFromHome.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.returning: - return " On The Way To Hospital".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.onTheWayToHospital.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.ended: - return "Arrived at Hospital".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.arrivedAtHospital.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.failed: - return "Failed".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.cancel: - return "Order Cancel".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.orderCancel.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart index 051c850..56a94e0 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -21,14 +21,12 @@ class PickupLocation extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Select Pickup Direction" - .needTranslation + LocaleKeys.selectPickupDirection.tr(context: context) .toText24(color: AppColors.textColor, isBold: true), SizedBox( height: 16.h, ), - "Select Direction" - .needTranslation + LocaleKeys.selectDirection.tr(context: context) .toText16(color: AppColors.textColor, weight: FontWeight.w600), SizedBox( height: 12.h, @@ -58,8 +56,7 @@ class PickupLocation extends StatelessWidget { activeColor: AppColors.primaryRedColor, fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), ), - "To Hospital" - .needTranslation + LocaleKeys.toHospital.tr(context: context) .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress(() { @@ -84,8 +81,7 @@ class PickupLocation extends StatelessWidget { activeColor: AppColors.primaryRedColor, fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), ), - "From Hospital" - .needTranslation + LocaleKeys.fromHospital.tr(context: context) .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress(() { @@ -105,8 +101,7 @@ class PickupLocation extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - "Select Way" - .needTranslation + LocaleKeys.selectWay.tr(context: context) .toText16(color: AppColors.textColor, weight: FontWeight.w600), SizedBox(height: 12.h), Row( @@ -128,8 +123,7 @@ class PickupLocation extends StatelessWidget { activeColor: AppColors.primaryRedColor, fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), ), - "One Way" - .needTranslation + LocaleKeys.oneWay.tr(context: context) .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) ], ).onPress(() { @@ -154,8 +148,7 @@ class PickupLocation extends StatelessWidget { activeColor: AppColors.primaryRedColor, fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), ), - "Two Way" - .needTranslation + LocaleKeys.twoWay.tr(context: context) .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress(() { diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart index 1b85165..3ae93d0 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart @@ -1,8 +1,10 @@ +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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.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/chip/app_custom_chip_widget.dart'; @@ -27,7 +29,7 @@ class TypeSelectionWidget extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ AppCustomChipWidget( - labelText: "All Facilities".needTranslation, + labelText: LocaleKeys.all.tr(context: context), shape: RoundedRectangleBorder( side: BorderSide( color: selectedFacility == FacilitySelection.ALL @@ -50,7 +52,7 @@ class TypeSelectionWidget extends StatelessWidget { child: AppCustomChipWidget( icon: AppAssets.hmg, iconHasColor: false, - labelText: "Hospitals".needTranslation, + labelText: LocaleKeys.hmgHospitals.tr(context: context), shape: RoundedRectangleBorder( side: BorderSide( color: selectedFacility == FacilitySelection.HMG @@ -74,7 +76,7 @@ class TypeSelectionWidget extends StatelessWidget { child: AppCustomChipWidget( icon: AppAssets.hmc, iconHasColor: false, - labelText: "Medical Centers".needTranslation, + labelText: LocaleKeys.hmcMedicalClinic.tr(context: context), shape: RoundedRectangleBorder( side: BorderSide( color: selectedFacility == FacilitySelection.HMC From 9795276ef9f493afa20925288d53adbe59b8b9d5 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 14 Jan 2026 12:56:45 +0300 Subject: [PATCH 40/46] fixed errors --- lib/core/dependencies.dart | 14 +-- lib/presentation/parking/paking_page.dart | 69 ++++++----- lib/presentation/parking/parking_slot.dart | 127 +++++++++------------ lib/routes/app_routes.dart | 2 +- 4 files changed, 97 insertions(+), 115 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0763e8e..be0ef7e 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -306,13 +306,13 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); + // getIt.registerFactory( + // () => QrParkingViewModel( + // qrParkingRepo: getIt(), + // errorHandlerService: getIt(), + // cacheService: getIt(), + // ), + // ); } } diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index ca72c6e..d9cc1d8 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -14,6 +14,7 @@ import '../../widgets/buttons/custom_button.dart'; import '../../widgets/routes/custom_page_route.dart'; + class ParkingPage extends StatefulWidget { const ParkingPage({super.key}); @@ -22,11 +23,11 @@ class ParkingPage extends StatefulWidget { } class _ParkingPageState extends State { + + Future _readQR(BuildContext context) async { final vm = context.read(); - final model = await vm.scanAndGetParking(); - if (model == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(vm.error ?? "Invalid Qr Code")), @@ -36,15 +37,38 @@ class _ParkingPageState extends State { Navigator.of(context).push( CustomPageRoute( - page: ParkingSlot(model: model), + page: ChangeNotifierProvider.value( + value: vm, + child: ParkingSlot(model: model), + ), ), ); } @override - Widget build(BuildContext context) { - final vm = context.watch(); // عشان loading + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + final vm = context.read(); + await vm.getIsSaveParking(); + if (!mounted) return; + if (vm.isSavePark && vm.qrParkingModel != null) { + Navigator.of(context).push( + CustomPageRoute( + page: ChangeNotifierProvider.value( + value: vm, + child: ParkingSlot(model: vm.qrParkingModel!), + ), + ), + ); + } + }); + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( @@ -98,7 +122,6 @@ class _ParkingPageState extends State { ), ), - /// Bottom button Container( decoration: RoundedRectangleBorder() .toSmoothCornerDecoration( @@ -113,40 +136,13 @@ class _ParkingPageState extends State { height: 56, child: CustomButton( text: "Read Barcodes".needTranslation, - onPressed: () => _readQR(context), // ALWAYS non-null - isDisabled: vm.isLoading, // control disabled state here + onPressed: () => _readQR(context), // always non-null + isDisabled: vm.isLoading, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, fontSize: 18, fontWeight: FontWeight.bold, - ) - - // ElevatedButton( - // style: ElevatedButton.styleFrom( - // backgroundColor: AppColors.primaryRedColor, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10), - // ), - // ), - // onPressed: vm.isLoading ? null : () => _readQR(context), - // child: vm.isLoading - // ? const SizedBox( - // width: 22, - // height: 22, - // child: CircularProgressIndicator( - // strokeWidth: 2, - // color: Colors.white, - // ), - // ) - // : const Text( - // "Read Barcodes", - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: Colors.white, - // ), - // ), - // ), + ), ), ), ), @@ -156,3 +152,4 @@ class _ParkingPageState extends State { } } + diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index ce13dad..0eb3718 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -5,7 +5,6 @@ import 'package:hmg_patient_app_new/core/app_export.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/qr_parking/models/qr_parking_response_model.dart'; - import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; @@ -13,7 +12,7 @@ import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; - +import '../../widgets/routes/custom_page_route.dart'; class ParkingSlot extends StatefulWidget { final QrParkingResponseModel model; @@ -28,7 +27,6 @@ class ParkingSlot extends StatefulWidget { } class _ParkingSlotState extends State { - void _openDirection() { final lat = widget.model.latitude; final lng = widget.model.longitude; @@ -36,8 +34,10 @@ class _ParkingSlotState extends State { final valid = lat != null && lng != null && !(lat == 0.0 && lng == 0.0) && - lat >= -90 && lat <= 90 && - lng >= -180 && lng <= 180; + lat >= -90 && + lat <= 90 && + lng >= -180 && + lng <= 180; if (!valid) { ScaffoldMessenger.of(context).showSnackBar( @@ -49,12 +49,33 @@ class _ParkingSlotState extends State { MapsLauncher.launchCoordinates(lat, lng); } + Future _resetDirection() async { final vm = context.read(); await vm.clearParking(); - Navigator.of(context).popUntil((route) => route.isFirst); + final model = await vm.scanAndGetParking(); + if (model == null) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(vm.error ?? "Scan cancelled")), + ); + + Navigator.of(context).pop(); + return; + } + + if (!mounted) return; + Navigator.of(context).pushReplacement( + CustomPageRoute( + page: ChangeNotifierProvider.value( + value: vm, + child: ParkingSlot(model: model), + ), + ), + ); } + DateTime? _parseDotNetDate(String? value) { if (value == null || value.isEmpty) return null; @@ -65,11 +86,9 @@ class _ParkingSlotState extends State { final milliseconds = int.tryParse(match.group(1)!); if (milliseconds == null) return null; - return DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true) - .toLocal(); + return DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: true).toLocal(); } - String _formatPrettyDate(String? value) { final date = _parseDotNetDate(value); if (date == null) return '-'; @@ -79,14 +98,13 @@ class _ParkingSlotState extends State { 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; - final day = date.day; + final day = date.day.toString().padLeft(2, '0'); final month = months[date.month - 1]; final year = date.year; - return "$day $month $year"; + return "$day $month $year"; // ✅ 15 Dec 2025 } - String _formatPrettyTime(String? value) { final date = _parseDotNetDate(value); if (date == null) return '-'; @@ -100,7 +118,7 @@ class _ParkingSlotState extends State { hour = hour % 12; if (hour == 0) hour = 12; - return "${hour.toString().padLeft(2, '0')}:$minute $period"; + return "${hour.toString().padLeft(2, '0')}:$minute $period"; // ✅ 03:05 PM } @override @@ -126,11 +144,9 @@ class _ParkingSlotState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Container( width: double.infinity, - decoration: RoundedRectangleBorder() - .toSmoothCornerDecoration( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, @@ -154,24 +170,16 @@ class _ParkingSlotState extends State { runSpacing: 4, children: [ AppCustomChipWidget( - labelText: - "Slot: ${widget.model.qRParkingCode ?? '-'}" - .needTranslation, + labelText: "Slot: ${widget.model.qRParkingCode ?? '-'}".needTranslation, ), AppCustomChipWidget( - labelText: - "Basement: ${widget.model.floorDescription ?? '-'}" - .needTranslation, + labelText: "Basement: ${widget.model.floorDescription ?? '-'}".needTranslation, ), AppCustomChipWidget( - labelText: - "Date: ${_formatPrettyDate(widget.model.createdOn)}" - .needTranslation, + labelText: "Date: ${_formatPrettyDate(widget.model.createdOn)}".needTranslation, ), AppCustomChipWidget( - labelText: - "Parked Since: ${_formatPrettyTime(widget.model.createdOn)}" - .needTranslation, + labelText: "Parked Since: ${_formatPrettyTime(widget.model.createdOn)}".needTranslation, ), ], ), @@ -179,13 +187,12 @@ class _ParkingSlotState extends State { ), ), ), - SizedBox(height: 24.h), SizedBox( width: double.infinity, height: 48.h, - child:CustomButton( + child: CustomButton( text: "Get Direction".needTranslation, onPressed: _openDirection, backgroundColor: AppColors.primaryRedColor, @@ -194,49 +201,25 @@ class _ParkingSlotState extends State { fontSize: 18, fontWeight: FontWeight.bold, borderRadius: 10, - ) - - // ElevatedButton( - // style: ElevatedButton.styleFrom( - // backgroundColor: AppColors.primaryRedColor, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10), - // ), - // ), - // onPressed: _openDirection, - // child: Text( - // "Get Direction".needTranslation, - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: AppColors.whiteColor, - // ), - // ), - // ), + ), ), - // const Spacer(), - // SizedBox( - // width: double.infinity, - // height: 48.h, - // child: OutlinedButton( - // style: OutlinedButton.styleFrom( - // side: BorderSide(color: AppColors.primaryRedColor), - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10), - // ), - // ), - // onPressed: _resetDirection, - // child: Text( - // "Reset Direction".needTranslation, - // style: TextStyle( - // fontSize: 16, - // fontWeight: FontWeight.w600, - // color: AppColors.primaryRedColor, - // ), - // ), - // ), - // ), + const Spacer(), + + SizedBox( + width: double.infinity, + height: 48.h, + child: CustomButton( + text: "Reset Direction".needTranslation, + onPressed: _resetDirection, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 18, + fontWeight: FontWeight.bold, + borderRadius: 10, + ), + ), ], ), ), @@ -249,3 +232,5 @@ class _ParkingSlotState extends State { } + + diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 778757d..632463f 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -138,6 +138,6 @@ class AppRoutes { qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), child: const ParkingPage(), - ),} + ), }; } From 87422e8e05ce1bab49c13aa973c6ed6ce74d76b6 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 14 Jan 2026 22:26:02 +0300 Subject: [PATCH 41/46] translation updates --- assets/langs/ar-SA.json | 122 ++++++++++++++- assets/langs/en-US.json | 118 ++++++++++++++- lib/core/api_consts.dart | 2 +- lib/core/dependencies.dart | 7 - lib/extensions/string_extensions.dart | 2 +- .../my_appointments_view_model.dart | 6 +- lib/generated/locale_keys.g.dart | 116 ++++++++++++++ .../RRT/rrt_map_screen.dart | 44 +++--- .../RRT/rrt_request_type_select.dart | 13 +- .../RRT/terms_and_condition.dart | 4 +- .../emergency_services_page.dart | 31 ++-- .../er_online_checkin_home.dart | 16 +- ...r_online_checkin_payment_details_page.dart | 12 +- .../er_online_checkin_payment_page.dart | 26 ++-- ...e_checkin_select_checkin_bottom_sheet.dart | 18 ++- .../history/er_history_listing.dart | 22 +-- .../history/widget/RequestStatus.dart | 12 +- .../widget/ambulance_history_item.dart | 6 +- .../history/widget/rrt_item.dart | 6 +- .../emergency_services/nearest_er_page.dart | 6 +- .../widgets/location_input_bottom_sheet.dart | 2 +- .../widgets/nearestERItem.dart | 8 +- .../habib_wallet/habib_wallet_page.dart | 2 +- .../habib_wallet/recharge_wallet_page.dart | 12 +- .../wallet_payment_confirm_page.dart | 12 +- .../widgets/select-medical_file.dart | 8 +- .../widgets/select_hospital_bottom_sheet.dart | 4 +- .../health_calculator_detailed_page.dart | 6 +- .../health_calculators_page.dart | 46 +++--- .../widgets/bf.dart | 18 ++- .../widgets/bmi.dart | 7 +- .../widgets/bmr.dart | 18 ++- .../widgets/calories.dart | 14 +- .../widgets/crabs.dart | 8 +- .../widgets/dduedate.dart | 2 +- .../widgets/ibw.dart | 14 +- .../widgets/ovulation.dart | 8 +- .../add_health_tracker_entry_page.dart | 48 +++--- .../health_tracker_detail_page.dart | 42 +++--- .../health_trackers/health_trackers_page.dart | 16 +- .../widgets/tracker_last_value_card.dart | 25 +-- .../hmg_services/services_page.dart | 76 +++++----- lib/presentation/home/landing_page.dart | 2 +- .../profile_settings/profile_settings.dart | 11 +- lib/routes/app_routes.dart | 2 +- pubspec.lock | 142 ++++++++++++++---- 46 files changed, 797 insertions(+), 345 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index da245da..19db982 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -525,7 +525,7 @@ "payOnline": "الدفع عبر الإنترنت", "cancelOrder": "إلغاء الطلب", "confirmAddress": "تأكيد العنوان ", - "confirmLocation": "��أكيد الموقع ", + "confirmLocation": "أكيد الموقع ", "conditionsHMG": "الشروط والأحكام ", "conditions": "الشروط والأحكام لكوم", "confirmDeleteMsg": "هل أنت متأكد! تريد الحذف ", @@ -1095,5 +1095,123 @@ "pickupFromHome": "الاستلام من المنزل", "onTheWayToHospital": " في الطريق إلى المستشفى", "arrivedAtHospital": "وصل إلى المستشفى", - "orderCancel": "إلغاء الطلب" + "orderCancel": "إلغاء الطلب", + "emergencyCheckIn": "تسجيل الطوارئ الإلكتروني", + "erOnlineCheckInDescription": "تتيح هذه الخدمة للمرضى تسجيل موعد الطوارئ قبل الوصول.", + "erOnlineCheckInSuccess": "تم تسجيل وصول الطوارئ بنجاح. الرجاء التوجه إلى منطقة الانتظار.", + "erOnlineCheckInError": "حدث خطأ غير متوقع أثناء عملية التسجيل. يرجى التواصل مع الدعم.", + "fetchingHospitalsList": "جاري جلب قائمة المستشفيات...", + "fetchingPaymentInformation": "جاري جلب معلومات الدفع...", + "erVisitDetails": "تفاصيل زيارة الطوارئ", + "erClinic": "عيادة الطوارئ", + "vatWithAmount": "الضريبة 15% ({amount})", + "erAppointmentBookedSuccess": "تم حجز موعدك بنجاح. يرجى إتمام إجراءات تسجيل الوصول عند وصولك إلى المستشفى.", + "underProcessing": "قيد المعالجة", + "canceledByPatient": "ملغى بواسطة المريض", + "rapidResponseTeam": "فريق الاستجابة السريع", + "allFacilities": "جميع المرافق", + "selectLocation": "اختر الموقع", + "pleaseSelectTheLocation": "يرجى اختيار الموقع", + "viewLocationGoogleMaps": "عرض الموقع على خرائط جوجل", + "callAmbulance": "استدعاء سيارة إسعاف", + "requestAmbulanceInEmergency": "طلب سيارة إسعاف في حالة الطوارئ من المنزل أو المستشفى", + "confirmation": "تأكيد", + "areYouSureYouWantToCallAmbulance": "هل أنت متأكد أنك تريد استدعاء سيارة إسعاف؟", + "getDetailsOfNearestBranch": "احصل على تفاصيل أقرب فرع بما في ذلك الاتجاهات", + "areYouSureYouWantToCallRRT": "هل أنت متأكد أنك تريد استدعاء فريق الاستجابة السريعة (RRT)؟", + "priorERCheckInToSkipLine": "تسجيل الوصول المسبق في الطوارئ لتجاوز الطابور والدفع في الاستقبال.", + "areYouSureYouWantToMakeERCheckIn": "هل أنت متأكد أنك تريد إجراء تسجيل وصول الطوارئ؟", + "checkingYourERAppointmentStatus": "جاري التحقق من حالة موعد الطوارئ الخاص بك...", + "transportOptions": "خيارات النقل", + "selectHospitalForAdvancePayment": "يرجى اختيار المستشفى الذي ترغب في دفع مبلغ مقدم له.", + "recharge": "إعادة الشحن", + "activityLevel": "مستوى النشاط", + "selectActivityLevel": "حدد مستوى النشاط", + "caloriesPerDay": "السعرات الحرارية في اليوم الواحد", + "dietType": "نوع النظام الغذائي", + "selectDietType": "حدد نوع النظام الغذائي", + "bodyFrameSize": "حجم إطار الجسم", + "selectBodyFrameSize": "حدد حجم إطار الجسم", + "averageCycleLength": "متوسط طول الدورة الشهرية (عادةً 28 يومًا)", + "averageLutealPhase": "متوسط طول المرحلة الأصفرية (عادةً 14 يومًا)", + "convert": "يتحول", + "calculate": "احسب", + "healthCalculators": "حاسبات الصحة", + "healthConverters": "محولات الصحة", + "generalHealth": "الصحة العامة", + "relatedToBMICalories": "متعلق بمؤشر كتلة الجسم والسعرات الحرارية ودهون الجسم وما إلى ذلك للبقاء على اطلاع دائم بصحتك.", + "selectCalculator": "اختر الآلة الحاسبة", + "womensHealth": "صحة المرأة", + "relatedToPeriodsOvulation": "متعلق بالدورة الشهرية والإباضة والحمل ومواضيع أخرى.", + "bloodSugar": "سكر الدم", + "trackYourGlucoseLevels": "تتبع مستويات الجلوكوز لديك، وفهم الاتجاهات، واحصل على رؤى مخصصة لصحة أفضل.", + "bloodCholesterol": "كوليسترول الدم", + "monitorCholesterolLevels": "راقب مستويات الكوليسترول، وقيّم مخاطر صحة القلب، واتخذ خطوات استباقية للرفاهية.", + "triglyceridesFatBlood": "الدهون الثلاثية في الدم", + "understandTriglyceridesImpact": "افهم تأثير الدهون الثلاثية على صحة القلب مع رؤى مخصصة وتوصيات الخبراء.", + "bmiCalculator": "حاسبة\nمؤشر كتلة الجسم", + "caloriesCalculator": "حاسبة\nالسعرات الحرارية", + "bmrCalculator": "حاسبة\nمعدل الأيض الأساسي", + "idealBodyWeight": "الوزن المثالي\nللجسم", + "bodyFatCalculator": "حاسبة\nدهون الجسم", + "carbsProteinFat": "الكربوهيدرات\nالبروتين والدهون", + "ovulationPeriod": "فترة\nالإباضة", + "deliveryDueDate": "تاريخ الولادة\nالمتوقع", + "low": "منخفض", + "preDiabetic": "ما قبل السكري", + "high": "مرتفع", + "elevated": "مرتفع قليلاً", + "recorded": "مسجل", + "noRecordsYet": "لا توجد سجلات بعد", + "lastRecord": "آخر سجل", + "addBloodSugar": "إضافة سكر الدم", + "addBloodPressure": "إضافة ضغط الدم", + "addWeight": "إضافة الوزن", + "bloodSugarDataSavedSuccessfully": "تم حفظ بيانات سكر الدم بنجاح", + "bloodPressureDataSavedSuccessfully": "تم حفظ بيانات ضغط الدم بنجاح", + "weightDataSavedSuccessfully": "تم حفظ بيانات الوزن بنجاح", + "pleaseWait": "يرجى الانتظار", + "selectUnit": "اختر الوحدة", + "selectMeasureTime": "اختر وقت القياس", + "selectArm": "اختر الذراع", + "enterBloodSugar": "أدخل سكر الدم", + "enterSystolicValue": "أدخل القيمة الانقباضية", + "enterDiastolicValue": "أدخل القيمة الانبساطية", + "enterWeight": "أدخل الوزن", + "selectDuration": "اختر المدة", + "systolic": "الانقباضي", + "diastolic": "الانبساطي", + "sendReportByEmail": "إرسال التقرير عبر البريد الإلكتروني", + "enterYourEmailToReceiveReport": "أدخل عنوان بريدك الإلكتروني لاستلام التقرير", + "addNewRecord": "إضافة سجل جديد", + "healthTrackers": "متتبعات الصحة", + "monitorBloodPressureLevels": "راقب مستويات ضغط الدم لديك، وتتبع القراءات الانقباضية والانبساطية، وحافظ على صحة قلبك.", + "trackWeightProgress": "تتبع تقدم وزنك، وضع الأهداف، وحافظ على كتلة جسم صحية من أجل العافية الشاملة.", + "bookAppointment": "حجز\nموعد", + "completeCheckup": "الفحص الشامل", + "indoorNavigation": "الملاحة الداخلية", + "eReferralServices": "خدمات الإحالة الإلكترونية", + "bloodDonation": "التبرع بالدم", + "dailyWaterMonitor": "مراقب الماء اليومي", + "fetchingYourWaterIntakeDetails": "جاري جلب تفاصيل استهلاك الماء الخاص بك.", + "healthCalculatorsServices": "حاسبات\nالصحة", + "healthConvertersServices": "محولات\nالصحة", + "smartWatchesServices": "الساعات\nالذكية", + "exploreServices": "استكشف الخدمات", + "medicalAndCareServices": "الخدمات الطبية والرعاية", + "hmgServices": "خدمات مجموعة الحبيب الطبية", + "personalServices": "الخدمات الشخصية", + "habibWallet": "محفظة الحبيب", + "loginToViewWalletBalance": "سجل الدخول لعرض رصيد محفظتك", + "recharge": "إعادة الشحن", + "loginToViewMedicalFile": "سجل الدخول لعرض ملفك الطبي", + "addMember": "إضافة عضو", + "addFamilyMember": "إضافة فرد من العائلة", + "pleaseFillBelowFieldToAddNewFamilyMember": "يرجى ملء الحقل أدناه لإضافة فرد جديد من العائلة إلى ملفك الشخصي", + "healthTools": "أدوات الصحة", + "supportServices": "خدمات الدعم", + "virtualTour": "جولة افتراضية", + "carParking": "موقف السيارات", + "latestNews": "آخر الأخبار", + "hmgContact": "اتصل بمجموعة الحبيب الطبية" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index f6500dc..ee489f5 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1091,5 +1091,121 @@ "pickupFromHome": "Pickup Up from Home", "onTheWayToHospital": " On The Way To Hospital", "arrivedAtHospital": "Arrived at Hospital", - "orderCancel": "Order Cancel" + "orderCancel": "Order Cancel", + "emergencyCheckIn": "Emergency Check-In", + "erOnlineCheckInDescription": "This service lets patients register their ER appointment prior to arrival.", + "erOnlineCheckInSuccess": "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.", + "erOnlineCheckInError": "Unexpected error occurred during check-in. Please contact support.", + "fetchingHospitalsList": "Fetching hospitals list...", + "fetchingPaymentInformation": "Fetching payment information...", + "erVisitDetails": "ER Visit Details", + "erClinic": "ER Clinic", + "vatWithAmount": "VAT 15% ({amount})", + "erAppointmentBookedSuccess": "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.", + "underProcessing": "Under processing", + "canceledByPatient": "Cancelled by patient", + "rapidResponseTeam": "Rapid Response Team", + "allFacilities": "All Facilities", + "selectLocation": "Select Location", + "pleaseSelectTheLocation": "Please select the location", + "viewLocationGoogleMaps": "View Location on Google Maps", + "callAmbulance": "Call Ambulance", + "requestAmbulanceInEmergency": "Request an ambulance in emergency from home or hospital", + "confirmation": "Confirmation", + "areYouSureYouWantToCallAmbulance": "Are you sure you want to call an ambulance?", + "getDetailsOfNearestBranch": "Get the details of nearest branch including directions", + "areYouSureYouWantToCallRRT": "Are you sure you want to call Rapid Response Team (RRT)?", + "priorERCheckInToSkipLine": "Prior ER Check-In to skip the line & payment at the reception.", + "areYouSureYouWantToMakeERCheckIn": "Are you sure you want to make ER Check-In?", + "checkingYourERAppointmentStatus": "Checking your ER Appointment status...", + "transportOptions": "Transport Options", + "selectHospitalForAdvancePayment": "Please select the hospital you want to make an advance payment for.", + "recharge": "Recharge", + "activityLevel": "Activity Level", + "selectActivityLevel": "Select Activity Level", + "caloriesPerDay": "Calories Per Day", + "dietType": "Diet Type", + "selectDietType": "Select Diet Type", + "bodyFrameSize": "Body Frame Size", + "selectBodyFrameSize": "Select Body Frame Size", + "averageCycleLength": "Average Cycle Length (Usually 28 days)", + "averageLutealPhase": "Average Luteal Phase Length(Usually 14 days)", + "convert": "Convert", + "calculate": "Calculate", + "healthCalculators": "Health Calculators", + "healthConverters": "Health Converters", + "generalHealth": "General Health", + "relatedToBMICalories": "Related To BMI, calories, body fat, etc to stay updated with your health.", + "selectCalculator": "Select Calculator", + "womensHealth": "Women's Health", + "relatedToPeriodsOvulation": "Related To periods, ovulation, pregnancy, and other topics.", + "bloodSugar": "Blood Sugar", + "trackYourGlucoseLevels": "Track your glucose levels, understand trends, and get personalized insights for better health.", + "bloodCholesterol": "Blood Cholesterol", + "monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.", + "triglyceridesFatBlood": "Triglycerides Fat Blood", + "understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.", + "bmiCalculator": "BMI\nCalculator", + "caloriesCalculator": "Calories\nCalculator", + "bmrCalculator": "BMR\nCalculator", + "idealBodyWeight": "Ideal Body\nWeight", + "bodyFatCalculator": "Body Fat\nCalculator", + "carbsProteinFat": "Carbs\nProtein & Fat", + "ovulationPeriod": "Ovulation\nPeriod", + "deliveryDueDate": "Delivery\nDue Date", + "low": "Low", + "preDiabetic": "Pre-diabetic", + "high": "High", + "elevated": "Elevated", + "recorded": "Recorded", + "noRecordsYet": "No records yet", + "lastRecord": "Last Record", + "addBloodSugar": "Add Blood Sugar", + "addBloodPressure": "Add Blood Pressure", + "addWeight": "Add Weight", + "bloodSugarDataSavedSuccessfully": "Blood Sugar Data saved successfully", + "bloodPressureDataSavedSuccessfully": "Blood Pressure Data saved successfully", + "weightDataSavedSuccessfully": "Weight Data saved successfully", + "pleaseWait": "Please wait", + "selectUnit": "Select Unit", + "selectMeasureTime": "Select Measure Time", + "selectArm": "Select Arm", + "enterBloodSugar": "Enter Blood Sugar", + "enterSystolicValue": "Enter Systolic Value", + "enterDiastolicValue": "Enter Diastolic Value", + "enterWeight": "Enter Weight", + "selectDuration": "Select Duration", + "systolic": "Systolic", + "diastolic": "Diastolic", + "sendReportByEmail": "Send Report by Email", + "enterYourEmailToReceiveReport": "Enter your email address to receive the report", + "addNewRecord": "Add new Record", + "healthTrackers": "Health Trackers", + "monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.", + "trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.", + "bookAppointment": "Book\nAppointment", + "completeCheckup": "Complete Checkup", + "indoorNavigation": "Indoor Navigation", + "eReferralServices": "E-Referral Services", + "dailyWaterMonitor": "Daily Water Monitor", + "fetchingYourWaterIntakeDetails": "Fetching your water intake details.", + "healthCalculatorsServices": "Health\nCalculators", + "healthConvertersServices": "Health\nConverters", + "smartWatchesServices": "Smart\nWatches", + "exploreServices": "Explore Services", + "medicalAndCareServices": "Medical & Care Services", + "hmgServices": "HMG Services", + "personalServices": "Personal Services", + "habibWallet": "Habib Wallet", + "loginToViewWalletBalance": "Login to view your wallet balance", + "loginToViewMedicalFile": "Login to view your medical file", + "addMember": "Add Member", + "addFamilyMember": "Add Family Member", + "pleaseFillBelowFieldToAddNewFamilyMember": "Please fill the below field to add a new family member to your profile", + "healthTools": "Health Tools", + "supportServices": "Support Services", + "virtualTour": "Virtual Tour", + "carParking": "Car Parking", + "latestNews": "Latest News", + "hmgContact": "HMG Contact" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 50be532..bdd7984 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -679,7 +679,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0763e8e..a1a04b5 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -166,13 +166,6 @@ class AppDependencies { ),); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 947bff4..309dde1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -15,7 +15,7 @@ extension CapExtension on String { String get allInCaps => toUpperCase(); - // String get needTranslation => this; + String get needTranslation => this; String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 3934bf7..96340a7 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -281,9 +281,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } - print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); - print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); - print('All Appointments: ${patientAppointmentsHistoryList.length}'); + debugPrint('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); + debugPrint('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); + debugPrint('All Appointments: ${patientAppointmentsHistoryList.length}'); getFiltersForSelectedAppointmentList(filteredAppointmentList); notifyListeners(); } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index fcc0257..689587c 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1092,5 +1092,121 @@ abstract class LocaleKeys { static const onTheWayToHospital = 'onTheWayToHospital'; static const arrivedAtHospital = 'arrivedAtHospital'; static const orderCancel = 'orderCancel'; + static const emergencyCheckIn = 'emergencyCheckIn'; + static const erOnlineCheckInDescription = 'erOnlineCheckInDescription'; + static const erOnlineCheckInSuccess = 'erOnlineCheckInSuccess'; + static const erOnlineCheckInError = 'erOnlineCheckInError'; + static const fetchingHospitalsList = 'fetchingHospitalsList'; + static const fetchingPaymentInformation = 'fetchingPaymentInformation'; + static const erVisitDetails = 'erVisitDetails'; + static const erClinic = 'erClinic'; + static const vatWithAmount = 'vatWithAmount'; + static const erAppointmentBookedSuccess = 'erAppointmentBookedSuccess'; + static const underProcessing = 'underProcessing'; + static const canceledByPatient = 'canceledByPatient'; + static const rapidResponseTeam = 'rapidResponseTeam'; + static const allFacilities = 'allFacilities'; + static const selectLocation = 'selectLocation'; + static const pleaseSelectTheLocation = 'pleaseSelectTheLocation'; + static const viewLocationGoogleMaps = 'viewLocationGoogleMaps'; + static const callAmbulance = 'callAmbulance'; + static const requestAmbulanceInEmergency = 'requestAmbulanceInEmergency'; + static const confirmation = 'confirmation'; + static const areYouSureYouWantToCallAmbulance = 'areYouSureYouWantToCallAmbulance'; + static const getDetailsOfNearestBranch = 'getDetailsOfNearestBranch'; + static const areYouSureYouWantToCallRRT = 'areYouSureYouWantToCallRRT'; + static const priorERCheckInToSkipLine = 'priorERCheckInToSkipLine'; + static const areYouSureYouWantToMakeERCheckIn = 'areYouSureYouWantToMakeERCheckIn'; + static const checkingYourERAppointmentStatus = 'checkingYourERAppointmentStatus'; + static const transportOptions = 'transportOptions'; + static const selectHospitalForAdvancePayment = 'selectHospitalForAdvancePayment'; + static const recharge = 'recharge'; + static const activityLevel = 'activityLevel'; + static const selectActivityLevel = 'selectActivityLevel'; + static const caloriesPerDay = 'caloriesPerDay'; + static const dietType = 'dietType'; + static const selectDietType = 'selectDietType'; + static const bodyFrameSize = 'bodyFrameSize'; + static const selectBodyFrameSize = 'selectBodyFrameSize'; + static const averageCycleLength = 'averageCycleLength'; + static const averageLutealPhase = 'averageLutealPhase'; + static const convert = 'convert'; + static const calculate = 'calculate'; + static const healthCalculators = 'healthCalculators'; + static const healthConverters = 'healthConverters'; + static const generalHealth = 'generalHealth'; + static const relatedToBMICalories = 'relatedToBMICalories'; + static const selectCalculator = 'selectCalculator'; + static const womensHealth = 'womensHealth'; + static const relatedToPeriodsOvulation = 'relatedToPeriodsOvulation'; + static const bloodSugar = 'bloodSugar'; + static const trackYourGlucoseLevels = 'trackYourGlucoseLevels'; + static const bloodCholesterol = 'bloodCholesterol'; + static const monitorCholesterolLevels = 'monitorCholesterolLevels'; + static const triglyceridesFatBlood = 'triglyceridesFatBlood'; + static const understandTriglyceridesImpact = 'understandTriglyceridesImpact'; + static const bmiCalculator = 'bmiCalculator'; + static const caloriesCalculator = 'caloriesCalculator'; + static const bmrCalculator = 'bmrCalculator'; + static const idealBodyWeight = 'idealBodyWeight'; + static const bodyFatCalculator = 'bodyFatCalculator'; + static const carbsProteinFat = 'carbsProteinFat'; + static const ovulationPeriod = 'ovulationPeriod'; + static const deliveryDueDate = 'deliveryDueDate'; + static const low = 'low'; + static const preDiabetic = 'preDiabetic'; + static const high = 'high'; + static const elevated = 'elevated'; + static const recorded = 'recorded'; + static const noRecordsYet = 'noRecordsYet'; + static const lastRecord = 'lastRecord'; + static const addBloodSugar = 'addBloodSugar'; + static const addBloodPressure = 'addBloodPressure'; + static const addWeight = 'addWeight'; + static const bloodSugarDataSavedSuccessfully = 'bloodSugarDataSavedSuccessfully'; + static const bloodPressureDataSavedSuccessfully = 'bloodPressureDataSavedSuccessfully'; + static const weightDataSavedSuccessfully = 'weightDataSavedSuccessfully'; + static const pleaseWait = 'pleaseWait'; + static const selectUnit = 'selectUnit'; + static const selectMeasureTime = 'selectMeasureTime'; + static const selectArm = 'selectArm'; + static const enterBloodSugar = 'enterBloodSugar'; + static const enterSystolicValue = 'enterSystolicValue'; + static const enterDiastolicValue = 'enterDiastolicValue'; + static const enterWeight = 'enterWeight'; + static const selectDuration = 'selectDuration'; + static const systolic = 'systolic'; + static const diastolic = 'diastolic'; + static const sendReportByEmail = 'sendReportByEmail'; + static const enterYourEmailToReceiveReport = 'enterYourEmailToReceiveReport'; + static const addNewRecord = 'addNewRecord'; + static const healthTrackers = 'healthTrackers'; + static const monitorBloodPressureLevels = 'monitorBloodPressureLevels'; + static const trackWeightProgress = 'trackWeightProgress'; + static const bookAppointment = 'bookAppointment'; + static const completeCheckup = 'completeCheckup'; + static const indoorNavigation = 'indoorNavigation'; + static const eReferralServices = 'eReferralServices'; + static const dailyWaterMonitor = 'dailyWaterMonitor'; + static const fetchingYourWaterIntakeDetails = 'fetchingYourWaterIntakeDetails'; + static const healthCalculatorsServices = 'healthCalculatorsServices'; + static const healthConvertersServices = 'healthConvertersServices'; + static const smartWatchesServices = 'smartWatchesServices'; + static const exploreServices = 'exploreServices'; + static const medicalAndCareServices = 'medicalAndCareServices'; + static const hmgServices = 'hmgServices'; + static const personalServices = 'personalServices'; + static const habibWallet = 'habibWallet'; + static const loginToViewWalletBalance = 'loginToViewWalletBalance'; + static const loginToViewMedicalFile = 'loginToViewMedicalFile'; + static const addMember = 'addMember'; + static const addFamilyMember = 'addFamilyMember'; + static const pleaseFillBelowFieldToAddNewFamilyMember = 'pleaseFillBelowFieldToAddNewFamilyMember'; + static const healthTools = 'healthTools'; + static const supportServices = 'supportServices'; + static const virtualTour = 'virtualTour'; + static const carParking = 'carParking'; + static const latestNews = 'latestNews'; + static const hmgContact = 'hmgContact'; } diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart index afe68b6..530152b 100644 --- a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -1,4 +1,3 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -135,19 +134,19 @@ class RrtMapScreen extends StatelessWidget { Column( spacing: 4.h, children: [ - "Select Location".needTranslation.toText21( + LocaleKeys.selectLocation.tr().toText21( weight: FontWeight.w600, color: AppColors.textColor, ), - "Please select the location".needTranslation.toText12( + LocaleKeys.pleaseSelectTheLocation.tr().toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) ], ), CustomButton( - text: "Submit Request".needTranslation, - onPressed: () { + text: LocaleKeys.submitRequest.tr(), + onPressed: () { LocationViewModel locationViewModel = context.read(); GeocodeResponse? response = locationViewModel.geocodeResponse; PlaceDetails? placeDetails = locationViewModel.placeDetails; @@ -235,7 +234,7 @@ class RrtMapScreen extends StatelessWidget { height: 40.h, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, - text: "Add new address".needTranslation, + text: LocaleKeys.addNewAddress.tr(), textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, onPressed: () {}, @@ -245,9 +244,7 @@ class RrtMapScreen extends StatelessWidget { isSelected: index == 0, address: "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", - title: index == 0 - ? "Home".needTranslation - : "Work".needTranslation, + title: index == 0 ? LocaleKeys.home.tr() : LocaleKeys.work.tr(), onTap: () {}, ); } @@ -291,8 +288,8 @@ class RrtMapScreen extends StatelessWidget { Row( children: [ hospitalAndPickUpItemContent( - title: "Pick".needTranslation, - subTitle: "Inside the home".needTranslation, + title: LocaleKeys.pick.tr(), + subTitle: LocaleKeys.insideTheHome.tr(), leadingIcon: AppAssets.pickup_bed, ), CustomSwitch( @@ -312,7 +309,7 @@ class RrtMapScreen extends StatelessWidget { children: [ hospitalAndPickUpItemContent( title: '', - subTitle: "Have any appointment".needTranslation, + subTitle: LocaleKeys.haveAnyAppointment.tr(), leadingIcon: AppAssets.appointment_checkin_icon, ), CustomSwitch( @@ -416,8 +413,8 @@ class RrtMapScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Total amount to pay".needTranslation.toText18( - weight: FontWeight.w600, + LocaleKeys.totalAmountToPay.tr().toText18( + weight: FontWeight.w600, color: AppColors.textColor, ), Row( @@ -425,9 +422,7 @@ class RrtMapScreen extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.warning, height: 18.h, width: 18.h), SizedBox(width: 4.h,), - "Amount will be paid at the hospital" - .needTranslation - .toText12( + LocaleKeys.amountPaidAtHospital.tr().toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), @@ -452,7 +447,7 @@ class RrtMapScreen extends StatelessWidget { ], ), CustomButton( - text: "Submit Request".needTranslation, + text: LocaleKeys.submitRequest.tr(), onPressed: () { LocationViewModel locationViewModel = context.read(); GeocodeResponse? response = locationViewModel.geocodeResponse; @@ -530,8 +525,8 @@ class RrtMapScreen extends StatelessWidget { return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, + labelText: LocaleKeys.enterPickupLocationManually.tr(), + hintText: LocaleKeys.enterPickupLocation.tr(), controller: TextEditingController( text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, @@ -562,7 +557,7 @@ class RrtMapScreen extends StatelessWidget { openLocationInputBottomSheet(BuildContext context) { context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( - title: "".needTranslation, + title: "", context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, @@ -583,11 +578,10 @@ class RrtMapScreen extends StatelessWidget { child: Row( children: [ hospitalAndPickUpItemContent( - title: "Select Hospital".needTranslation, + title: "Select Hospital".tr(), subTitle: context .read() - .getSelectedHospitalName() ?? - "Select Hospital".needTranslation, + .getSelectedHospitalName() ?? "Select Hospital".tr(), leadingIcon: AppAssets.hospital, ), Utils.buildSvgWithAssets( @@ -606,7 +600,7 @@ class RrtMapScreen extends StatelessWidget { void openAppointmentList(BuildContext context) { showCommonBottomSheetWithoutHeight( - title: "Select Appointment".needTranslation, + title: LocaleKeys.selectAppointment.tr(), context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .5, diff --git a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart index 13e0106..91db0de 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -18,12 +18,11 @@ class RrtRequestTypeSelect extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer(builder: (context, emergencyServicesVM, child) { - print("the checkbox is ${emergencyServicesVM.agreedToTermsAndCondition}"); return Column( children: [ Column( children: [ - "Rapid Response Team (RRT) options".needTranslation.toText20(color: AppColors.textColor, isBold: true), + LocaleKeys.rapidResponseTeam.tr(context: context).toText20(color: AppColors.textColor, isBold: true), SizedBox( height: 16.h, ), @@ -74,7 +73,7 @@ class RrtRequestTypeSelect extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Total amount to pay".needTranslation.toText18( + LocaleKeys.totalAmountToPay.tr(context: context).toText18( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -94,19 +93,19 @@ class RrtRequestTypeSelect extends StatelessWidget { SizedBox( width: 4.h, ), - "Amount will be paid at the hospital".needTranslation.toText11( + LocaleKeys.amountPaidAtHospital.tr(context: context).toText11( color: AppColors.greyTextColor, ), ], ), Row( children: [ - "+ VAT 15%(".needTranslation.toText12( + LocaleKeys.vat15.tr(context: context).toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), - "${emergencyServicesVM.selectedRRTProcedure?.patientTaxAmount})".needTranslation.toText14( - weight: FontWeight.w600, + "${emergencyServicesVM.selectedRRTProcedure?.patientTaxAmount})".toText14( + weight: FontWeight.w600, color: AppColors.greyTextColor, ), ], diff --git a/lib/presentation/emergency_services/RRT/terms_and_condition.dart b/lib/presentation/emergency_services/RRT/terms_and_condition.dart index 1d1dac1..bd0d6fd 100644 --- a/lib/presentation/emergency_services/RRT/terms_and_condition.dart +++ b/lib/presentation/emergency_services/RRT/terms_and_condition.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.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/extensions/widget_extensions.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'; @@ -18,7 +20,7 @@ class TermsAndCondition extends StatelessWidget { Expanded( child: CollapsingListView( - title: "Terms And Condition".needTranslation, + title: LocaleKeys.termsConditoins.tr(context: context), child:DecoratedBox(decoration:RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 3833d32..a5281ca 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -59,9 +59,8 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Request an ambulance in emergency from home or hospital" - .needTranslation + LocaleKeys.callAmbulance.tr().toText16(isBold: true, color: AppColors.blackColor), + LocaleKeys.requestAmbulanceInEmergency.tr() .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), @@ -100,10 +99,9 @@ class EmergencyServicesPage extends StatelessWidget { Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + LocaleKeys.confirmation.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Are you sure you want to call an ambulance?" - .needTranslation + LocaleKeys.areYouSureYouWantToCallAmbulance.tr() .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( @@ -148,9 +146,8 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Nearest ER Location".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Get the details of nearest branch including directions" - .needTranslation + LocaleKeys.nearester.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), + LocaleKeys.getDetailsOfNearestBranch.tr() .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), @@ -178,7 +175,7 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Rapid Response Team (RRT)".toText16(isBold: true, color: AppColors.blackColor), + LocaleKeys.rapidResponseTeam.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), "Comprehensive medical service for all sorts of urgent and stable cases" .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], @@ -204,8 +201,7 @@ class EmergencyServicesPage extends StatelessWidget { SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Are you sure you want to call Rapid Response Team (RRT)?" - .needTranslation + LocaleKeys.areYouSureYouWantToCallRRT.tr() .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( @@ -278,9 +274,8 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Emergency Check-In".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Prior ER Check-In to skip the line & payment at the reception." - .needTranslation + LocaleKeys.emergencyCheckIn.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), + LocaleKeys.priorERCheckInToSkipLine.tr() .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), @@ -318,13 +313,13 @@ class EmergencyServicesPage extends StatelessWidget { SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Are you sure you want to make ER Check-In?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + LocaleKeys.areYouSureYouWantToMakeERCheckIn.tr().toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), onPressed: () async { Navigator.of(context).pop(); - LoaderBottomSheet.showLoader(loadingText: "Checking your ER Appointment status...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingYourERAppointmentStatus.tr()); await context.read().checkPatientERAdvanceBalance(onSuccess: (dynamic response) { LoaderBottomSheet.hideLoader(); context.read().navigateToEROnlineCheckIn(); @@ -389,7 +384,7 @@ class EmergencyServicesPage extends StatelessWidget { void openTranportationSelectionBottomSheet(BuildContext context) { if (emergencyServicesViewModel.transportationOptions.isNotEmpty) { showCommonBottomSheetWithoutHeight( - title: "Transport Options".needTranslation, + title: LocaleKeys.transportOptions.tr(), context, child: SizedBox( height: 400.h, diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index ee061b7..3850c1f 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -38,7 +38,7 @@ class ErOnlineCheckinHome extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Emergency Check-In".needTranslation, + title: LocaleKeys.emergencyCheckIn.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(24.h), @@ -53,8 +53,8 @@ class ErOnlineCheckinHome extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Online Check-In".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "This service lets patients to register their ER appointment prior to arrival.".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -74,7 +74,7 @@ class ErOnlineCheckinHome extends StatelessWidget { showNfcReader(context, onNcfScan: (String nfcId) { Future.delayed(const Duration(milliseconds: 100), () async { print(nfcId); - LoaderBottomSheet.showLoader(loadingText: "Processing check-in...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.processingCheckIn.tr()); await emergencyServicesViewModel.getProjectIDFromNFC( nfcCode: nfcId, onSuccess: (value) async { @@ -84,7 +84,7 @@ class ErOnlineCheckinHome extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.onlineCheckIn.tr(), - child: Utils.getSuccessWidget(loadingText: "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.erOnlineCheckInSuccess.tr()), callBackFunc: () { Navigator.pushAndRemoveUntil( context, @@ -98,7 +98,7 @@ class ErOnlineCheckinHome extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Unexpected error occurred during check-in. Please contact support.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.erOnlineCheckInError.tr()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -117,7 +117,7 @@ class ErOnlineCheckinHome extends StatelessWidget { // callBackFunc: () {}, // isFullScreen: false); } else { - LoaderBottomSheet.showLoader(loadingText: "Fetching hospitals list...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingHospitalsList.tr()); await context.read().getProjects(); LoaderBottomSheet.hideLoader(); //Project Selection Dropdown @@ -155,7 +155,7 @@ class ErOnlineCheckinHome extends StatelessWidget { onHospitalClicked: (hospital) async { Navigator.pop(context); vm.setSelectedHospital(hospital); - LoaderBottomSheet.showLoader(loadingText: "Fetching payment information...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPaymentInformation.tr(context: context)); await vm.getPatientERPaymentInformation(onSuccess: (response) { LoaderBottomSheet.hideLoader(); vm.navigateToEROnlineCheckInPaymentPage(); diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart index f48efe7..0534854 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart @@ -34,7 +34,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: "Emergency Check-In".needTranslation, + title: LocaleKeys.emergencyCheckIn.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(24.h), @@ -52,7 +52,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "ER Visit Details".needTranslation.toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.erVisitDetails.tr().toText18(color: AppColors.textColor, isBold: true), SizedBox(height: 24.h), Row( children: [ @@ -70,7 +70,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( - labelText: "ER Clinic".needTranslation, + labelText: LocaleKeys.erClinic.tr(), labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( @@ -111,7 +111,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText18(isBold: true), + LocaleKeys.amountBeforeTax.tr().toText18(isBold: true), Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], @@ -121,8 +121,8 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: "".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), - "VAT 15% (${emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount})" - .needTranslation + LocaleKeys.vatWithAmount + .tr(namedArgs: {"amount": emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString()}) .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), ], ), diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index 8de7518..70a5b08 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -80,7 +80,7 @@ class _ErOnlineCheckinPaymentPageState extends State children: [ Expanded( child: CollapsingListView( - title: "Emergency Check-In".needTranslation, + title: LocaleKeys.emergencyCheckIn.tr(context: context), child: SingleChildScrollView( child: Column( children: [ @@ -99,7 +99,7 @@ class _ErOnlineCheckinPaymentPageState extends State children: [ Image.asset(AppAssets.mada, width: 72.h, height: 25.h), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true), + LocaleKeys.mada.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -141,7 +141,7 @@ class _ErOnlineCheckinPaymentPageState extends State ], ), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -178,7 +178,7 @@ class _ErOnlineCheckinPaymentPageState extends State children: [ Image.asset(AppAssets.tamaraEng, width: 72.h, height: 25.h), SizedBox(height: 16.h), - "Tamara".needTranslation.toText16(isBold: true), + LocaleKeys.tamara.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), @@ -229,8 +229,8 @@ class _ErOnlineCheckinPaymentPageState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), - CustomButton( + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { Navigator.of(context).push( @@ -253,12 +253,12 @@ class _ErOnlineCheckinPaymentPageState extends State ) : const SizedBox(), SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], @@ -266,7 +266,7 @@ class _ErOnlineCheckinPaymentPageState extends State Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), @@ -276,7 +276,7 @@ class _ErOnlineCheckinPaymentPageState extends State Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), ], @@ -425,7 +425,7 @@ class _ErOnlineCheckinPaymentPageState extends State } void checkPaymentStatus() async { - LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); await payfortViewModel.checkPaymentStatus( transactionID: transID, onSuccess: (apiResponse) async { @@ -445,7 +445,7 @@ class _ErOnlineCheckinPaymentPageState extends State if (emergencyServicesViewModel.isERBookAppointment) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -458,7 +458,7 @@ class _ErOnlineCheckinPaymentPageState extends State LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart index d44686c..3b2dc8a 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,6 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -35,8 +37,8 @@ class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { children: [ checkInOptionCard( AppAssets.checkin_location_icon, - "Live Location".needTranslation, - "Verify your location to be at hospital to check in".needTranslation, + LocaleKeys.liveLocation.tr(context: context), + LocaleKeys.verifyYourLocationAtHospital.tr(context: context), ).onPress(() { // locationUtils = LocationUtils( // isShowConfirmDialog: false, @@ -51,8 +53,8 @@ class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); } else { showCommonBottomSheetWithoutHeight(context, - title: "Error".needTranslation, - child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + title: LocaleKeys.error.tr(context: context), + child: Utils.getErrorWidget(loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context),), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); } @@ -61,8 +63,8 @@ class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_nfc_icon, - "NFC (Near Field Communication)".needTranslation, - "Scan your phone via NFC board to check in".needTranslation, + LocaleKeys.nfcNearFieldCommunication.tr(context: context), + LocaleKeys.scanPhoneViaNFC.tr(context: context), ).onPress(() { Future.delayed(const Duration(milliseconds: 500), () { showNfcReader(context, onNcfScan: (String nfcId) { @@ -75,8 +77,8 @@ class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_qr_icon, - "QR Code".needTranslation, - "Scan QR code with your camera to check in".needTranslation, + LocaleKeys.qrCode.tr(context: context), + LocaleKeys.scanQRCodeToCheckIn.tr(context: context), ).onPress(() async { String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); if (onlineCheckInQRCode != "") { diff --git a/lib/presentation/emergency_services/history/er_history_listing.dart b/lib/presentation/emergency_services/history/er_history_listing.dart index fe1ed31..a60d455 100644 --- a/lib/presentation/emergency_services/history/er_history_listing.dart +++ b/lib/presentation/emergency_services/history/er_history_listing.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart' show AppAssets; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/ambulance_history_item.dart' show AmbulanceHistoryItem; import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/rrt_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -24,7 +26,7 @@ class ErHistoryListing extends StatelessWidget { Expanded( child: CollapsingListView( - title: "History Log".needTranslation, + title: LocaleKeys.history.tr(context: context), child: SingleChildScrollView( physics: NeverScrollableScrollPhysics(), child: Column( @@ -55,12 +57,10 @@ class ErHistoryListing extends StatelessWidget { }), ), Visibility( - visible: data.$1 - ?.isEmpty == true, child: Center( - child: Utils.getNoDataWidget(context, - noDataText: "You don't have any history" - .needTranslation), - )), + visible: data.$1.isEmpty == true, + child: Center( + child: Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDataAvailable.tr(context: context)), + )), ], ); } @@ -92,9 +92,9 @@ class ErHistoryListing extends StatelessWidget { return Row( spacing: 8.h, children: [ - if(dataList?.isNotEmpty == true) + if(dataList.isNotEmpty == true) AppCustomChipWidget( - labelText: "All Facilities".needTranslation, + labelText: LocaleKeys.allFacilities.tr(context: context), shape: RoundedRectangleBorder( side: BorderSide( color: value == OrderDislpay.ALL ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, @@ -111,7 +111,7 @@ class ErHistoryListing extends StatelessWidget { .ambulanceOrders ?.isNotEmpty == true) AppCustomChipWidget( - labelText: "Ambulance".needTranslation, + labelText: LocaleKeys.ambulancerequest.tr(context: context), icon: AppAssets.ambulance, shape: RoundedRectangleBorder( side: BorderSide( @@ -130,7 +130,7 @@ class ErHistoryListing extends StatelessWidget { ?.completedOrders .isNotEmpty == true) AppCustomChipWidget( - labelText: "Rapid Response Team".needTranslation, + labelText: LocaleKeys.rapidResponseTeam.tr(context: context), icon: AppAssets.ic_rrt_vehicle, shape: RoundedRectangleBorder( side: BorderSide( diff --git a/lib/presentation/emergency_services/history/widget/RequestStatus.dart b/lib/presentation/emergency_services/history/widget/RequestStatus.dart index 4f39a49..849565a 100644 --- a/lib/presentation/emergency_services/history/widget/RequestStatus.dart +++ b/lib/presentation/emergency_services/history/widget/RequestStatus.dart @@ -1,8 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.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/chip/app_custom_chip_widget.dart'; - class RequestStatus extends StatelessWidget { final int status; @@ -21,14 +22,13 @@ class RequestStatus extends StatelessWidget { switch (status) { case 1: //pending case 2: //processing - return "Under Processing".needTranslation; - case 3: //completed - return "Completed".needTranslation; - break; + return LocaleKeys.underProcessing.tr(); + case 3: + return LocaleKeys.completed.tr(); case 4: //cancel case 6: case 7: - return "Canceled by patient".needTranslation; + return LocaleKeys.canceledByPatient.tr(); break; } return null; diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart index f3ec388..7b17eab 100644 --- a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -40,7 +42,7 @@ class AmbulanceHistoryItem extends StatelessWidget { spacing: 4.w, children: [ chip( Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), - chip("Ambulance".needTranslation, AppAssets.ambulance, AppColors.blackBgColor), + chip(LocaleKeys.ambulancerequest.tr(context: context), AppAssets.ambulance, AppColors.blackBgColor), ], ), Row( @@ -52,7 +54,7 @@ class AmbulanceHistoryItem extends StatelessWidget { ), if (order.statusId == 1 || order.statusId == 2) CustomButton( - text: "Cancel Request".needTranslation, + text: LocaleKeys.cancelRequest.tr(context: context), onPressed: () async { openCancelOrderBottomSheet(context); }, diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart index dfb6e79..d345e2d 100644 --- a/lib/presentation/emergency_services/history/widget/rrt_item.dart +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -40,13 +42,13 @@ class RRTItem extends StatelessWidget { spacing: 4.w, children: [ chip( Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), - chip("Rapid Response Team(RRT)".needTranslation, AppAssets.ic_rrt_vehicle, AppColors.blackBgColor), + chip(LocaleKeys.rapidResponseTeam.tr(context: context), AppAssets.ic_rrt_vehicle, AppColors.blackBgColor), ], ), SizedBox(height: 4.h), if (order.statusId == 1 || order.statusId == 2) CustomButton( - text: "Cancel Request".needTranslation, + text: LocaleKeys.cancelRequest.tr(context: context), onPressed: () async { openCancelOrderBottomSheet(context); }, diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index 16863bf..0ea3ab8 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -36,7 +36,7 @@ class _NearestErPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Nearest ER".needTranslation, + title: LocaleKeys.nearester.tr(context: context), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -44,7 +44,7 @@ class _NearestErPageState extends State { children: [ TextInputWidget( labelText: LocaleKeys.search.tr(), - hintText: 'Type any facility name'.needTranslation, + hintText: 'Type any facility name', controller: searchText, onChange: (value) { debouncer.run(() { @@ -92,7 +92,7 @@ class _NearestErPageState extends State { }, ); } else { - return Center(child: Utils.getNoDataWidget(context, noDataText: "No nearest Er Arround you".needTranslation)); + return Center(child: Utils.getNoDataWidget(context, noDataText: "No nearest Er Around you")); } }), ), diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart index c5301ea..db45654 100644 --- a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -31,7 +31,7 @@ class LocationInputBottomSheet extends StatelessWidget { children: [ TextInputWidget( labelText: LocaleKeys.search.tr(), - hintText: "Search Location".needTranslation, + hintText: LocaleKeys.selectLocation.tr(context: context), controller: TextEditingController(), onChange: (value){ debouncer.run(() { diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index bbce56a..2226f82 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/emergency_services/models/resp_model/ProjectAvgERWaitingTime.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:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -67,14 +69,14 @@ class NearestERItem extends StatelessWidget { spacing: 8.h, children: [ AppCustomChipWidget( - labelText: "${nearestERItem.distanceInKilometers} km".needTranslation, + labelText: "${nearestERItem.distanceInKilometers} km", icon: AppAssets.location, iconHasColor: false, labelPadding: EdgeInsetsDirectional.only(start: 4.h, end: 0.h), padding: EdgeInsets.all(8.h), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: "Expected waiting time: ${nearestERItem.getTime()} mins".needTranslation, + labelText: "Expected waiting time: ${nearestERItem.getTime()} mins", icon: AppAssets.waiting_time_clock, iconHasColor: false, labelPadding: EdgeInsetsDirectional.only(start: 4.h, end: 0.h), @@ -87,7 +89,7 @@ class NearestERItem extends StatelessWidget { children: [ Expanded( child: CustomButton( - text: "View Location on Google Maps".needTranslation, + text: LocaleKeys.viewLocationGoogleMaps.tr(context: context), iconSize: 18.h, icon: AppAssets.location, onPressed: () { diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index a7ec23d..b096e94 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -84,7 +84,7 @@ class _HabibWalletState extends State { CustomButton( icon: AppAssets.recharge_icon, iconSize: 21.h, - text: "Recharge".needTranslation, + text: LocaleKeys.recharge.tr(context: context), onPressed: () { Navigator.of(context) .push( diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 74eebec..33de5e6 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -80,7 +80,7 @@ class _RechargeWalletPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ //TODO: Check with hussain to show AED or SAR - "Enter an amount".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.amount.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), Spacer(), Row( crossAxisAlignment: CrossAxisAlignment.end, @@ -110,7 +110,7 @@ class _RechargeWalletPageState extends State { 13.h, isExpanded: false), const Spacer(), - "SAR".needTranslation.toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ], @@ -151,7 +151,7 @@ class _RechargeWalletPageState extends State { ], ).onPress(() async { habibWalletVM.setCurrentIndex(0); - showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.medicalFile.tr(context: context), titleWidget: Consumer(builder: (context, habibWalletVM, child) { return habibWalletVM.currentIndex != 0 ? IconButton( @@ -160,7 +160,7 @@ class _RechargeWalletPageState extends State { onPressed: () => habibWalletVM.setCurrentIndex(0), highlightColor: Colors.transparent, ) - : "Select Medical File".needTranslation.toText20(weight: FontWeight.w600); + : LocaleKeys.medicalFile.tr(context: context).toText20(weight: FontWeight.w600); }), child: Consumer(builder: (context, habibWalletVM, child) { return MultiPageBottomSheet(); }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); @@ -261,7 +261,7 @@ class _RechargeWalletPageState extends State { if (amountTextController.text.isEmpty) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Please enter amount to continue.".needTranslation), + child: Utils.getErrorWidget(loadingText: "Please enter amount to continue."), callBackFunc: () { textFocusNode.requestFocus(); }, @@ -271,7 +271,7 @@ class _RechargeWalletPageState extends State { } else if (habibWalletVM.selectedHospital == null) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Please select hospital to continue.".needTranslation), + child: Utils.getErrorWidget(loadingText: "Please select hospital to continue."), callBackFunc: () { textFocusNode.requestFocus(); }, diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 1341af7..3dec5aa 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -82,7 +82,7 @@ class _WalletPaymentConfirmPageState extends State { children: [ Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: false), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: false), + LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: false), ], ), SizedBox(width: 8.h), @@ -124,7 +124,7 @@ class _WalletPaymentConfirmPageState extends State { ], ).toShimmer2(isShow: false), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: false), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: false), ], ), SizedBox(width: 8.h), @@ -180,7 +180,7 @@ class _WalletPaymentConfirmPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Total amount to pay".needTranslation.toText16(isBold: true), + LocaleKeys.totalAmountToPay.tr(context: context).toText16(isBold: true), Utils.getPaymentAmountWithSymbol(habibWalletVM.walletRechargeAmount.toString().toText24(isBold: true), AppColors.blackColor, 15.h, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), @@ -314,7 +314,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Payment Successful!".needTranslation), + child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { Navigator.of(context).pop(); Navigator.of(context).pop(); @@ -327,7 +327,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed - ${err}".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -339,7 +339,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart index 73a7dfe..aaa0036 100644 --- a/lib/presentation/habib_wallet/widgets/select-medical_file.dart +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -76,7 +76,7 @@ class _MultiPageBottomSheetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Enter File Number".needTranslation.toText20(weight: FontWeight.w600), + "Enter File Number".toText20(weight: FontWeight.w600), SizedBox(height: 12.h), TextInputWidget( labelText: LocaleKeys.fileNumber.tr(), @@ -98,9 +98,9 @@ class _MultiPageBottomSheetState extends State { await habibWalletVM.getPatientInfoByPatientID( patientID: fileNumberEditingController.text, onSuccess: (response) async { - print(response.data["GetPatientInfoByPatientIDList"][0]["FullName"]); + debugPrint(response.data["GetPatientInfoByPatientIDList"][0]["FullName"]); await _dialogService.showCommonBottomSheetWithoutH( - message: "A file was found with name: ${response.data["GetPatientInfoByPatientIDList"][0]["FullName"]}, Would you like to recharge wallet for this file number?".needTranslation, + message: "A file was found with name: ${response.data["GetPatientInfoByPatientIDList"][0]["FullName"]}, Would you like to recharge wallet for this file number?", label: LocaleKeys.notice.tr(), onOkPressed: () { habibWalletVM.setSelectedRechargeType(3); @@ -161,7 +161,7 @@ class _MultiPageBottomSheetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "Select a medical file from your family".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "Select a medical file from your family".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), diff --git a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart index 086e0da..da374a0 100644 --- a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart +++ b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/hospital_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:provider/provider.dart'; @@ -28,7 +30,7 @@ class SelectHospitalBottomSheet extends StatelessWidget { // ), // ), Text( - "Please select the hospital you want to make an advance payment for.".needTranslation, + LocaleKeys.selectHospitalForAdvancePayment.tr(context: context), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, 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 42cba5d..4a83f51 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 @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart'; @@ -57,8 +59,8 @@ class _HealthCalculatorDetailedPageState extends State { Widget build(BuildContext context) { DialogService dialogService = getIt.get(); return CollapsingListView( - title: widget.type == HealthCalConEnum.calculator ? "Health Calculators".needTranslation : "Health Converters".needTranslation, + title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(), child: widget.type == HealthCalConEnum.calculator ? Column( children: [ @@ -47,8 +49,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - "General Health".needTranslation.toText16(weight: FontWeight.w600), - "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + LocaleKeys.generalHealth.tr().toText16(weight: FontWeight.w600), + LocaleKeys.relatedToBMICalories.tr().toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) ], ), ), @@ -60,7 +62,7 @@ class _HealthCalculatorsPageState extends State { ).paddingAll(16.w)) .onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Calculator".needTranslation, + label: LocaleKeys.selectCalculator.tr(), message: "", child: showCalculatorsItems(type: HealthCalculatorEnum.general), onOkPressed: () {}, @@ -78,8 +80,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - "Women's Health".needTranslation.toText16(weight: FontWeight.w600), - "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + LocaleKeys.womensHealth.tr().toText16(weight: FontWeight.w600), + LocaleKeys.relatedToPeriodsOvulation.tr().toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) ], ), ), @@ -89,7 +91,7 @@ class _HealthCalculatorsPageState extends State { ).paddingAll(16.w)) .onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Calculator".needTranslation, + label: LocaleKeys.selectCalculator.tr(), message: "", child: showCalculatorsItems(type: HealthCalculatorEnum.women), onOkPressed: () {}, @@ -111,8 +113,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - "Blood Sugar".needTranslation.toText16(weight: FontWeight.w600), - "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation.toText12( + LocaleKeys.bloodSugar.tr().toText16(weight: FontWeight.w600), + LocaleKeys.trackYourGlucoseLevels.tr().toText12( fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3), ) @@ -145,9 +147,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - "Blood Cholesterol".needTranslation.toText16(weight: FontWeight.w600), - "Monitor your cholesterol levels, track your LDL, HDL, and triglycerides. Get personalized recommendations for a healthy heart." - .needTranslation + LocaleKeys.bloodCholesterol.tr().toText16(weight: FontWeight.w600), + LocaleKeys.monitorCholesterolLevels.tr() .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) ], ), @@ -176,9 +177,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - "Triglycerides Fat Blood".needTranslation.toText16(weight: FontWeight.w600), - "Manage triglycerides, a key blood fat. Understand levels, diet impacts, and heart health strategies." - .needTranslation + LocaleKeys.triglyceridesFatBlood.tr().toText16(weight: FontWeight.w600), + LocaleKeys.understandTriglyceridesImpact.tr() .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) ], ), @@ -230,17 +230,17 @@ class _HealthCalculatorsPageState extends State { } final List generalHealthServices = [ - HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi, clinicID: 108, calculationID: null), - HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories, clinicID: null, calculationID: 2), - HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr, clinicID: null, calculationID: 3), - HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight, clinicID: null, calculationID: 4), - HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat, clinicID: null, calculationID: 5), - HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat, clinicID: null, calculationID: 11), + HealthComponentModel(title: LocaleKeys.bmiCalculator.tr(), icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi, clinicID: 108, calculationID: null), + HealthComponentModel(title: LocaleKeys.caloriesCalculator.tr(), icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories, clinicID: null, calculationID: 2), + HealthComponentModel(title: LocaleKeys.bmrCalculator.tr(), icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr, clinicID: null, calculationID: 3), + HealthComponentModel(title: LocaleKeys.idealBodyWeight.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight, clinicID: null, calculationID: 4), + HealthComponentModel(title: LocaleKeys.bodyFatCalculator.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat, clinicID: null, calculationID: 5), + HealthComponentModel(title: LocaleKeys.carbsProteinFat.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat, clinicID: null, calculationID: 11), ]; final List womenHealthServices = [ - HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation, clinicID: null, calculationID: 6 ), - HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate, clinicID: null, calculationID: 6), + HealthComponentModel(title: LocaleKeys.ovulationPeriod.tr(), icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation, clinicID: null, calculationID: 6 ), + HealthComponentModel(title: LocaleKeys.deliveryDueDate.tr(), icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate, clinicID: null, calculationID: 6), ]; } diff --git a/lib/presentation/health_calculators_and_converts/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart index 3a88eb3..b00846b 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -92,7 +94,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.selectGender.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedGender.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -105,7 +107,7 @@ class _BodyFatWidgetState extends State { ).paddingSymmetrical(0.w, 16.w).onPress(() { List _genders = ["Male", "Female"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Gender".needTranslation, + label: LocaleKeys.selectGender.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -215,7 +217,7 @@ class _BodyFatWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -323,7 +325,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedNeckUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -332,7 +334,7 @@ class _BodyFatWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -449,7 +451,7 @@ class _BodyFatWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -557,7 +559,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedHipUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -566,7 +568,7 @@ class _BodyFatWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart index 49b77c0..3969db4 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -10,6 +11,8 @@ 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/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import '../../../generated/locale_keys.g.dart'; + class BMIWidget extends StatefulWidget { Function(dynamic result)? onChange; @@ -116,7 +119,7 @@ class _BMIWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -233,7 +236,7 @@ class _BMIWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart index 5f429ff..f19c6b5 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -87,7 +89,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.selectGender.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedGender.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -101,7 +103,7 @@ class _BMRWidgetState extends State { List _genders = ["Male", "Female"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Gender".needTranslation, + label: LocaleKeys.selectGender.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -168,7 +170,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Age (11-120) yrs".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Age (11-120) yrs".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -240,7 +242,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -249,7 +251,7 @@ class _BMRWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -367,7 +369,7 @@ class _BMRWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -437,7 +439,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Activity Level".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.activityLevel.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -450,7 +452,7 @@ class _BMRWidgetState extends State { ).paddingSymmetrical(0.w, 16.w).onPress(() { List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Activity Level".needTranslation, + label: LocaleKeys.selectActivityLevel.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart index fbe15a6..b1a4b77 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/calories.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -102,7 +104,7 @@ class _CaloriesWidgetState extends State { List _genders = ["Male", "Female"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Gender".needTranslation, + label: LocaleKeys.selectGender.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -168,7 +170,7 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Age (11-120) yrs".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Age (11-120) yrs".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -249,7 +251,7 @@ class _CaloriesWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -366,7 +368,7 @@ class _CaloriesWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -436,7 +438,7 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Activity Level".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.activityLevel.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -449,7 +451,7 @@ class _CaloriesWidgetState extends State { ).paddingSymmetrical(0.w, 16.w).onPress(() { List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Activity Level".needTranslation, + label: LocaleKeys.selectActivityLevel.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart index 5fe1215..83130fe 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -68,7 +70,7 @@ class _CrabsWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Calories Per Day".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.caloriesPerDay.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -111,7 +113,7 @@ class _CrabsWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Diet Type".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.dietType.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedDietType.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -124,7 +126,7 @@ class _CrabsWidgetState extends State { ).paddingSymmetrical(0.w, 16.w).onPress(() { List _activity = ["Very Low Crabs", "Low Crabs", "Moderate Crabs", "USDA Guidelines ", "Zone Diet"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Diet Type".needTranslation, + label: LocaleKeys.selectDietType.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart index 15c0a0c..3ecf2a5 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart @@ -36,7 +36,7 @@ class _DeliveryDueDWidgetState extends State { children: [ TextInputWidget( labelText: "Last Period Date", - hintText: "11 July, 1994".needTranslation, + hintText: "11 July, 1994", controller: _date, focusNode: FocusNode(), isEnable: true, diff --git a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart index 5f67511..1665ccd 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -114,7 +116,7 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -123,7 +125,7 @@ class _IdealBodyWeightWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -231,7 +233,7 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -240,7 +242,7 @@ class _IdealBodyWeightWidgetState extends State { ], ).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Unit".needTranslation, + label: LocaleKeys.unit.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -308,7 +310,7 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Body Frame Size".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.bodyFrameSize.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), selectedBodyFrameSize.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), ], ), @@ -321,7 +323,7 @@ class _IdealBodyWeightWidgetState extends State { ).paddingSymmetrical(0.w, 16.w).onPress(() { List _activity = ["Small (fingers overlaps)", "Medium (fingers touch)", "Large (fingers don't touch)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Body Frame Size".needTranslation, + label: LocaleKeys.selectBodyFrameSize.tr(context: context), message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), diff --git a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart index de209cb..8d7591e 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; @@ -63,7 +65,7 @@ class _OvulationWidgetState extends State { children: [ TextInputWidget( labelText: "Date", - hintText: "11 July, 1994".needTranslation, + hintText: "11 July, 1994", controller: _ageController, isEnable: true, prefix: null, @@ -95,7 +97,7 @@ class _OvulationWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Average Cycle Length (Usually 28 days)".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.averageCycleLength.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -132,7 +134,7 @@ class _OvulationWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Average Luteal Phase Length(Usually 14 days)".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.averageLutealPhase.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, diff --git a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart index 56f82fc..cf92c9b 100644 --- a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart +++ b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/core/enums.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -55,11 +57,11 @@ class _AddHealthTrackerEntryPageState extends State { String _getPageTitle() { switch (widget.trackerType) { case HealthTrackerTypeEnum.bloodSugar: - return "Add Blood Sugar".needTranslation; + return LocaleKeys.addBloodSugar.tr(context: context); case HealthTrackerTypeEnum.bloodPressure: - return "Add Blood Pressure".needTranslation; + return LocaleKeys.addBloodPressure.tr(context: context); case HealthTrackerTypeEnum.weightTracker: - return "Add Weight".needTranslation; + return LocaleKeys.addWeight.tr(context: context); } } @@ -67,11 +69,11 @@ class _AddHealthTrackerEntryPageState extends State { String _getSuccessMessage() { switch (widget.trackerType) { case HealthTrackerTypeEnum.bloodSugar: - return "Blood Sugar Data saved successfully".needTranslation; + return LocaleKeys.bloodSugarDataSavedSuccessfully.tr(context: context); case HealthTrackerTypeEnum.bloodPressure: - return "Blood Pressure Data saved successfully".needTranslation; + return LocaleKeys.bloodPressureDataSavedSuccessfully.tr(context: context); case HealthTrackerTypeEnum.weightTracker: - return "Weight Data saved successfully".needTranslation; + return LocaleKeys.weightDataSavedSuccessfully.tr(context: context); } } @@ -92,7 +94,7 @@ class _AddHealthTrackerEntryPageState extends State { // Save Blood Sugar entry Future _saveBloodSugarEntry(HealthTrackersViewModel viewModel) async { - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); // Combine date and time final dateTime = "${dateController.text} ${timeController.text}"; @@ -113,7 +115,7 @@ class _AddHealthTrackerEntryPageState extends State { // Save Weight entry Future _saveWeightEntry(HealthTrackersViewModel viewModel) async { - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); // Combine date and time final dateTime = "${dateController.text} ${timeController.text}"; @@ -133,7 +135,7 @@ class _AddHealthTrackerEntryPageState extends State { // Save Blood Pressure entry Future _saveBloodPressureEntry(HealthTrackersViewModel viewModel) async { - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); // Combine date and time final dateTime = "${dateController.text} ${timeController.text}"; @@ -204,7 +206,7 @@ class _AddHealthTrackerEntryPageState extends State { bool useUpperCase = false, }) { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.7), @@ -237,7 +239,7 @@ class _AddHealthTrackerEntryPageState extends State { FocusScope.of(context).unfocus(); _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.bloodSugarUnit, selectedValue: viewModel.selectedBloodSugarUnit, onSelected: viewModel.setBloodSugarUnit, @@ -250,7 +252,7 @@ class _AddHealthTrackerEntryPageState extends State { FocusScope.of(context).unfocus(); _showSelectionBottomSheet( context: context, - title: "Select Measure Time".needTranslation, + title: LocaleKeys.selectMeasureTime.tr(context: context), items: viewModel.bloodSugarMeasureTimeEnList, selectedValue: viewModel.selectedBloodSugarMeasureTime, onSelected: viewModel.setBloodSugarMeasureTime, @@ -263,7 +265,7 @@ class _AddHealthTrackerEntryPageState extends State { FocusScope.of(context).unfocus(); _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.weightUnits, selectedValue: viewModel.selectedWeightUnit, onSelected: viewModel.setWeightUnit, @@ -276,7 +278,7 @@ class _AddHealthTrackerEntryPageState extends State { FocusScope.of(context).unfocus(); _showSelectionBottomSheet( context: context, - title: "Select Arm".needTranslation, + title: LocaleKeys.selectArm.tr(context: context), items: viewModel.measuredArmList, selectedValue: viewModel.selectedMeasuredArm, onSelected: viewModel.setMeasuredArm, @@ -404,7 +406,7 @@ class _AddHealthTrackerEntryPageState extends State { children: [ _buildSettingsRow( icon: AppAssets.heightIcon, - label: "Enter Blood Sugar".needTranslation, + label: LocaleKeys.enterBloodSugar.tr(context: context), inputField: _buildTextField(viewModel.bloodSugarController, '', keyboardType: TextInputType.number), unit: viewModel.selectedBloodSugarUnit, onUnitTap: () => _showBloodSugarUnitSelectionBottomSheet(context, viewModel), @@ -413,7 +415,7 @@ class _AddHealthTrackerEntryPageState extends State { Divider(height: 1, color: AppColors.dividerColor), _buildSettingsRow( icon: AppAssets.weight_tracker_icon, - label: "Select Measure Time".needTranslation, + label: LocaleKeys.selectMeasureTime.tr(context: context), value: viewModel.selectedBloodSugarMeasureTime, onRowTap: () => _showBloodSugarEntryTimeBottomSheet(context, viewModel), ), @@ -428,19 +430,19 @@ class _AddHealthTrackerEntryPageState extends State { _buildSettingsRow( icon: AppAssets.bloodPressureIcon, iconColor: AppColors.greyTextColor, - label: "Enter Systolic Value".needTranslation, + label: LocaleKeys.enterSystolicValue.tr(context: context), inputField: _buildTextField(viewModel.systolicController, '', keyboardType: TextInputType.number), ), _buildSettingsRow( icon: AppAssets.bloodPressureIcon, iconColor: AppColors.greyTextColor, - label: "Enter Diastolic Value".needTranslation, + label: LocaleKeys.enterDiastolicValue.tr(context: context), inputField: _buildTextField(viewModel.diastolicController, '', keyboardType: TextInputType.number), ), _buildSettingsRow( icon: AppAssets.bodyIcon, iconColor: AppColors.greyTextColor, - label: "Select Arm".needTranslation, + label: LocaleKeys.selectArm.tr(context: context), value: viewModel.selectedMeasuredArm, onRowTap: () => _showMeasuredArmSelectionBottomSheet(context, viewModel), ), @@ -455,7 +457,7 @@ class _AddHealthTrackerEntryPageState extends State { children: [ _buildSettingsRow( icon: AppAssets.weightScale, - label: "Enter Weight".needTranslation, + label: LocaleKeys.enterWeight.tr(context: context), inputField: _buildTextField(viewModel.weightController, '', keyboardType: TextInputType.number), unit: viewModel.selectedWeightUnit, onUnitTap: () => _showWeightUnitSelectionBottomSheet(context, viewModel), @@ -474,7 +476,7 @@ class _AddHealthTrackerEntryPageState extends State { isReadOnly: true, isArrowTrailing: true, labelText: "Date", - hintText: "Select date".needTranslation, + hintText: LocaleKeys.pickADate.tr(context: context), focusNode: FocusNode(), isEnable: true, prefix: null, @@ -504,7 +506,7 @@ class _AddHealthTrackerEntryPageState extends State { isReadOnly: true, isArrowTrailing: true, labelText: "Time", - hintText: "Select time".needTranslation, + hintText: LocaleKeys.selectMeasureTime.tr(context: context), focusNode: FocusNode(), isEnable: true, prefix: null, @@ -547,7 +549,7 @@ class _AddHealthTrackerEntryPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: "Save".needTranslation, + text: LocaleKeys.save.tr(context: context), onPressed: () async => await _saveEntry(viewModel), borderRadius: 12.r, padding: EdgeInsets.symmetric(vertical: 14.h), diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart index 9443f12..83b37f6 100644 --- a/lib/presentation/health_trackers/health_tracker_detail_page.dart +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -1,3 +1,4 @@ +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'; @@ -14,6 +15,7 @@ import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/ import 'package:hmg_patient_app_new/features/health_trackers/models/blood_sugar/year_diabetic_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/widgets/tracker_last_value_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -67,11 +69,11 @@ class _HealthTrackerDetailPageState extends State { String _getPageTitle() { switch (widget.trackerType) { case HealthTrackerTypeEnum.bloodSugar: - return "Blood Sugar".needTranslation; + return LocaleKeys.bloodSugar.tr(context: context); case HealthTrackerTypeEnum.bloodPressure: - return "Blood Pressure".needTranslation; + return LocaleKeys.bloodPressure.tr(context: context); case HealthTrackerTypeEnum.weightTracker: - return "Weight".needTranslation; + return LocaleKeys.weight.tr(context: context); } } @@ -153,7 +155,7 @@ class _HealthTrackerDetailPageState extends State { final dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -183,7 +185,7 @@ class _HealthTrackerDetailPageState extends State { void _showHistoryDurationBottomsheet(BuildContext context, HealthTrackersViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Duration".needTranslation, + title: LocaleKeys.selectDuration.tr(), items: viewModel.durationFilters, selectedValue: viewModel.selectedDurationFilter, onSelected: viewModel.setFilterDuration, @@ -286,7 +288,7 @@ class _HealthTrackerDetailPageState extends State { children: [ Row( children: [ - "History".needTranslation.toText16(isBold: true), + LocaleKeys.history.tr(context: context).toText16(isBold: true), if (viewModel.isGraphView) ...[ SizedBox(width: 12.w), InkWell( @@ -661,9 +663,9 @@ class _HealthTrackerDetailPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildLegendItem(AppColors.errorColor, "Systolic".needTranslation), + _buildLegendItem(AppColors.errorColor, LocaleKeys.systolic.tr()), SizedBox(width: 24.w), - _buildLegendItem(AppColors.blueColor, "Diastolic".needTranslation), + _buildLegendItem(AppColors.blueColor, LocaleKeys.diastolic.tr()), ], ), SizedBox(height: 12.h), @@ -1046,7 +1048,7 @@ class _HealthTrackerDetailPageState extends State { final emailController = TextEditingController(text: userEmail); dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Send Report by Email".needTranslation, + label: LocaleKeys.sendReportByEmail.tr(), message: "", child: _buildEmailInputContent( context: context, @@ -1068,7 +1070,7 @@ class _HealthTrackerDetailPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Enter your email address to receive the report".needTranslation.toText14( + LocaleKeys.enterYourEmailToReceiveReport.tr().toText14( color: AppColors.textColor, weight: FontWeight.w400, ), @@ -1077,8 +1079,8 @@ class _HealthTrackerDetailPageState extends State { // Email Input Field using TextInputWidget TextInputWidget( padding: EdgeInsets.symmetric(horizontal: 8.w), - labelText: "Email Address".needTranslation, - hintText: "Enter email address".needTranslation, + labelText: LocaleKeys.email.tr(context: context), + hintText: LocaleKeys.enterEmail.tr(context: context), controller: emailController, keyboardType: TextInputType.emailAddress, isEnable: true, @@ -1094,7 +1096,7 @@ class _HealthTrackerDetailPageState extends State { Expanded( child: CustomButton( height: 56.h, - text: "Send Report".needTranslation, + text: LocaleKeys.send.tr(context: context), onPressed: () { _sendEmailReport( context: context, @@ -1122,7 +1124,7 @@ class _HealthTrackerDetailPageState extends State { // Validate email if (email.isEmpty) { dialogService.showErrorBottomSheet( - message: "Please enter your email address".needTranslation, + message: LocaleKeys.enterEmail.tr(context: context), ); return; } @@ -1131,7 +1133,7 @@ class _HealthTrackerDetailPageState extends State { final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(email)) { dialogService.showErrorBottomSheet( - message: "Please enter a valid email address".needTranslation, + message: LocaleKeys.pleaseEnterAValidEmail.tr(context: context), ); return; } @@ -1142,7 +1144,7 @@ class _HealthTrackerDetailPageState extends State { // Call appropriate email function based on tracker type switch (widget.trackerType) { case HealthTrackerTypeEnum.bloodSugar: - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); await viewModel.sendBloodSugarReportByEmail( email: email, onSuccess: () { @@ -1158,7 +1160,7 @@ class _HealthTrackerDetailPageState extends State { break; case HealthTrackerTypeEnum.bloodPressure: - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); await viewModel.sendBloodPressureReportByEmail( email: email, @@ -1176,7 +1178,7 @@ class _HealthTrackerDetailPageState extends State { break; case HealthTrackerTypeEnum.weightTracker: - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context)); await viewModel.sendWeightReportByEmail( email: email, onSuccess: () { @@ -1199,7 +1201,7 @@ class _HealthTrackerDetailPageState extends State { showCommonBottomSheetWithoutHeight( context, child: Utils.getSuccessWidget( - loadingText: "Report has been sent to your email successfully".needTranslation, + loadingText: LocaleKeys.emailSentSuccessfully.tr(context: context), ), callBackFunc: () {}, isCloseButtonVisible: false, @@ -1261,7 +1263,7 @@ class _HealthTrackerDetailPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: "Add new Record".needTranslation, + text: LocaleKeys.addNewRecord.tr(), onPressed: () { if (!viewModel.isLoading) { context.navigateWithName(AppRoutes.addHealthTrackerEntryPage, arguments: widget.trackerType); diff --git a/lib/presentation/health_trackers/health_trackers_page.dart b/lib/presentation/health_trackers/health_trackers_page.dart index c55e8b4..9c68281 100644 --- a/lib/presentation/health_trackers/health_trackers_page.dart +++ b/lib/presentation/health_trackers/health_trackers_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/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'; @@ -70,14 +72,14 @@ class _HealthTrackersPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Health Trackers".needTranslation, + title: LocaleKeys.healthTrackers.tr(context: context), child: Column( children: [ buildHealthTrackerCard( iconBgColor: AppColors.primaryRedColor, icon: AppAssets.bloodSugarOnlyIcon, - title: "Blood Sugar".needTranslation, - description: "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation, + title: LocaleKeys.bloodSugar.tr(context: context), + description: "Track your glucose levels, understand trends, and get personalized insights for better health.", onTap: () { context.navigateWithName( AppRoutes.healthTrackerDetailPage, @@ -89,8 +91,8 @@ class _HealthTrackersPageState extends State { buildHealthTrackerCard( iconBgColor: AppColors.infoColor, icon: AppAssets.bloodPressureIcon, - title: "Blood Pressure".needTranslation, - description: "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.".needTranslation, + title: LocaleKeys.bloodPressure.tr(context: context), + description: LocaleKeys.monitorBloodPressureLevels.tr(context: context), onTap: () { context.navigateWithName( AppRoutes.healthTrackerDetailPage, @@ -102,8 +104,8 @@ class _HealthTrackersPageState extends State { buildHealthTrackerCard( iconBgColor: AppColors.successColor, icon: AppAssets.weightIcon, - title: "Weight".needTranslation, - description: "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.".needTranslation, + title: LocaleKeys.weight.tr(context: context), + description: LocaleKeys.trackWeightProgress.tr(context: context), onTap: () { context.navigateWithName( AppRoutes.healthTrackerDetailPage, diff --git a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart index 542baa5..570005b 100644 --- a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart +++ b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -19,32 +20,32 @@ class TrackerLastValueCard extends StatelessWidget { /// Get status text and color based on blood sugar value (String status, Color color, Color bgColor) _getBloodSugarStatus(double value) { if (value < 70) { - return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); + return (LocaleKeys.low.tr(), AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); } else if (value <= 100) { - return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + return (LocaleKeys.normal.tr(), AppColors.successColor, AppColors.successLightBgColor); } else if (value <= 125) { - return ('Pre-diabetic'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); + return (LocaleKeys.preDiabetic.tr(), AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); } else { - return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); + return (LocaleKeys.high.tr(), AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); } } /// Get status text and color based on blood pressure value (systolic) (String status, Color color, Color bgColor) _getBloodPressureStatus(int systolic) { if (systolic < 90) { - return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); + return (LocaleKeys.low.tr(), AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5)); } else if (systolic <= 120) { - return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + return (LocaleKeys.normal.tr(), AppColors.successColor, AppColors.successLightBgColor); } else if (systolic <= 140) { - return ('Elevated'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); + return (LocaleKeys.elevated.tr(), AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4)); } else { - return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); + return (LocaleKeys.high.tr(), AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4)); } } /// Get status for weight (neutral - no good/bad status) (String status, Color color, Color bgColor) _getWeightStatus() { - return ('Recorded'.needTranslation, AppColors.successColor, AppColors.successLightBgColor); + return (LocaleKeys.recorded.tr(), AppColors.successColor, AppColors.successLightBgColor); } /// Get default unit based on tracker type @@ -219,7 +220,7 @@ class TrackerLastValueCard extends StatelessWidget { ), SizedBox(height: 8.h), AppCustomChipWidget( - labelText: "No records yet".needTranslation, + labelText: LocaleKeys.noRecordsYet.tr(), icon: AppAssets.doctor_calendar_icon, ), ], @@ -249,13 +250,13 @@ class TrackerLastValueCard extends StatelessWidget { Row( children: [ AppCustomChipWidget( - labelText: "${"Last Record".needTranslation}: $formattedDate", + labelText: "${LocaleKeys.lastRecord.tr()}: $formattedDate", icon: AppAssets.doctor_calendar_icon, ), SizedBox(width: 8.w), if (trackerType != HealthTrackerTypeEnum.weightTracker) ...[ AppCustomChipWidget( - labelText: status.needTranslation, + labelText: status, icon: AppAssets.normalStatusGreenIcon, iconColor: statusColor, ), diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 5c028db..0b37a4d 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -50,7 +50,7 @@ class ServicesPage extends StatelessWidget { late MedicalFileViewModel medicalFileViewModel; late final List hmgServices = [ - HmgServicesComponentModel(11, "Emergency Services".needTranslation, "".needTranslation, AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { + HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().flushData(); getIt.get().getTransportationOrders( @@ -71,14 +71,14 @@ class ServicesPage extends StatelessWidget { }), HmgServicesComponentModel( 11, - "Book\nAppointment".needTranslation, - "".needTranslation, + LocaleKeys.bookAppointment.tr(), + "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: AppRoutes.bookAppointmentPage, ), - HmgServicesComponentModel(5, "Complete Checkup".needTranslation, "".needTranslation, AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); } else { @@ -87,8 +87,8 @@ class ServicesPage extends StatelessWidget { }), HmgServicesComponentModel( 11, - "Indoor Navigation".needTranslation, - "".needTranslation, + LocaleKeys.indoorNavigation.tr(), + "", AppAssets.indoor_nav_icon, bgColor: Color(0xff45A2F8), true, @@ -131,7 +131,7 @@ class ServicesPage extends StatelessWidget { }, ), HmgServicesComponentModel( - 11, "E-Referral Services".needTranslation, "".needTranslation, AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.eReferralPage); } else { @@ -140,13 +140,13 @@ class ServicesPage extends StatelessWidget { }), HmgServicesComponentModel( 3, - "Blood Donation".needTranslation, - "".needTranslation, + LocaleKeys.bloodDonation.tr(), + "", AppAssets.blood_donation_icon, bgColor: AppColors.bloodDonationCardColor, true, route: null, onTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr()); await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { // await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { LoaderBottomSheet.hideLoader(); @@ -210,8 +210,8 @@ class ServicesPage extends StatelessWidget { late final List hmgHealthToolServices = [ HmgServicesComponentModel( 11, - "Health Trackers".needTranslation, - "".needTranslation, + LocaleKeys.healthTrackers.tr(), + "", AppAssets.general_health, bgColor: AppColors.whiteColor, true, @@ -226,15 +226,15 @@ class ServicesPage extends StatelessWidget { ), HmgServicesComponentModel( 11, - "Daily Water Monitor".needTranslation, - "".needTranslation, + LocaleKeys.dailyWaterMonitor.tr(), + "", AppAssets.daily_water_monitor_icon, bgColor: AppColors.whiteColor, true, route: null, // Set to null since we handle navigation in onTap onTap: () async { if (getIt.get().isAuthenticated) { - LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); final waterMonitorVM = getIt.get(); final context = getIt.get().navigatorKey.currentContext!; await waterMonitorVM.fetchUserDetailsForMonitoring( @@ -259,8 +259,8 @@ class ServicesPage extends StatelessWidget { ), HmgServicesComponentModel( 11, - "Health\nCalculators".needTranslation, - "".needTranslation, + LocaleKeys.healthCalculatorsServices.tr(), + "", AppAssets.health_calculators_services_icon, bgColor: AppColors.whiteColor, true, @@ -268,8 +268,8 @@ class ServicesPage extends StatelessWidget { ), HmgServicesComponentModel( 5, - "Health\nConverters".needTranslation, - "".needTranslation, + LocaleKeys.healthConvertersServices.tr(), + "", AppAssets.health_converters_icon, bgColor: AppColors.whiteColor, true, @@ -277,8 +277,8 @@ class ServicesPage extends StatelessWidget { ), HmgServicesComponentModel( 11, - "Smart\nWatches".needTranslation, - "".needTranslation, + LocaleKeys.smartWatchesServices.tr(), + "", AppAssets.smartwatch_icon, bgColor: AppColors.whiteColor, true, @@ -301,13 +301,13 @@ class ServicesPage extends StatelessWidget { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Explore Services".needTranslation, + title: LocaleKeys.exploreServices.tr(), isLeading: false, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - "Medical & Care Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -325,7 +325,7 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - "HMG Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), SizedBox( height: 350.h, @@ -356,7 +356,7 @@ class ServicesPage extends StatelessWidget { ), ), SizedBox(height: 24.h), - "Personal Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Row( children: [ @@ -377,7 +377,7 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 30.w, height: 30.h), - "Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), ], ), @@ -387,7 +387,7 @@ class ServicesPage extends StatelessWidget { return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); }) - : "Login to view your wallet balance".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), + : LocaleKeys.loginToViewWalletBalance.tr().toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), getIt.get().isAuthenticated ? CustomButton( @@ -396,7 +396,7 @@ class ServicesPage extends StatelessWidget { iconSize: 16.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, - text: "Recharge".needTranslation, + text: LocaleKeys.recharge.tr(), borderWidth: 0.w, fontWeight: FontWeight.w500, borderColor: Colors.transparent, @@ -472,7 +472,7 @@ class ServicesPage extends StatelessWidget { ), ], ) - : "Login to view your medical file".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), + : LocaleKeys.loginToViewMedicalFile.tr().toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), getIt.get().isAuthenticated ? CustomButton( @@ -481,7 +481,7 @@ class ServicesPage extends StatelessWidget { iconSize: 16.w, iconColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor, - text: "Add Member".needTranslation, + text: LocaleKeys.addMember.tr(), borderWidth: 0.w, fontWeight: FontWeight.w500, borderColor: Colors.transparent, @@ -492,8 +492,8 @@ class ServicesPage extends StatelessWidget { DialogService dialogService = getIt.get(); medicalFileViewModel.clearAuthValues(); dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), onVerificationPress: () { medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); @@ -517,7 +517,7 @@ class ServicesPage extends StatelessWidget { ], ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - "Health Tools".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -540,7 +540,7 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - "Support Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -565,7 +565,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - "Virtual Tour".needTranslation.toText12(fontWeight: FontWeight.w500) + LocaleKeys.virtualTour.tr().toText12(fontWeight: FontWeight.w500) ], ), ), @@ -594,7 +594,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) + LocaleKeys.carParking.tr().toText12(fontWeight: FontWeight.w500) ], ).onPress(() { Navigator.push( @@ -633,7 +633,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - "Latest News".needTranslation.toText12(fontWeight: FontWeight.w500) + LocaleKeys.latestNews.tr().toText12(fontWeight: FontWeight.w500) ], ), ), @@ -662,7 +662,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - "HMG Contact".needTranslation.toText12(fontWeight: FontWeight.w500) + LocaleKeys.hmgContact.tr().toText12(fontWeight: FontWeight.w500) ], ), ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 789bc4d..f336c5b 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -113,7 +113,7 @@ class _LandingPageState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); emergencyServicesViewModel.checkPatientERAdvanceBalance(); - myAppointmentsViewModel.getPatientAppointmentQueueDetails(); + // myAppointmentsViewModel.getPatientAppointmentQueueDetails(); notificationsViewModel.initNotificationsViewModel(); // Commented as per new requirement to remove rating popup from the app diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 62f090e..1c16439 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -150,7 +150,7 @@ class ProfileSettingsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), "Habib Wallet".needTranslation.toText16(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), @@ -193,13 +193,10 @@ class ProfileSettingsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.language_change, "Language".needTranslation, () { - showCommonBottomSheetWithoutHeight(context, - title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); + actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); }, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation), 1.divider, - actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}), - 1.divider, actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}), 1.divider, actionItem(AppAssets.touch_face_id, "Touch ID / Face ID Services".needTranslation, () {}, switchValue: true), @@ -236,7 +233,7 @@ class ProfileSettingsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () { + actionItem(AppAssets.call_fill, LocaleKeys.contactUs.tr(context: context), () { launchUrl(Uri.parse("tel://" + "+966 11 525 9999")); }, trailingLabel: "011 525 9999"), 1.divider, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 778757d..fc6fed8 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -138,6 +138,6 @@ class AppRoutes { qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), child: const ParkingPage(), - ),} + ) }; } diff --git a/pubspec.lock b/pubspec.lock index 42b828d..7e5aaf5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.59" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" amazon_payfort: dependency: "direct main" description: @@ -61,10 +69,10 @@ packages: dependency: "direct main" description: name: barcode_scan2 - sha256: "0f3eb7c0a0c80a0f65d3fa88737544fdb6d27127a4fad566e980e626f3fb76e1" + sha256: "50b286021c644deee71e20a06c1709adc6594e39d65024ced0458cc1e3ff298e" url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.6.0" boolean_selector: dependency: transitive description: @@ -193,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_jsonwebtoken: + dependency: "direct main" + description: + name: dart_jsonwebtoken + sha256: "0de65691c1d736e9459f22f654ddd6fd8368a271d4e41aa07e53e6301eff5075" + url: "https://pub.dev" + source: hosted + version: "3.3.1" dartz: dependency: "direct main" description: @@ -218,6 +234,38 @@ packages: url: "https://github.com/bardram/device_calendar" source: git version: "4.3.1" + device_calendar_plus: + dependency: "direct main" + description: + name: device_calendar_plus + sha256: d11a70d98eb123e8eb09fdcfaf220ca4f1aa65a1512e12092f176f4b54983507 + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_android: + dependency: transitive + description: + name: device_calendar_plus_android + sha256: a341ef29fa0251251287d63c1d009dfd35c1459dc6a129fd5e03f5ac92d8d7ff + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_ios: + dependency: transitive + description: + name: device_calendar_plus_ios + sha256: "3b2f84ce1ed002be8460e214a3229e66748bbaad4077603f2c734d67c42033ff" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_platform_interface: + dependency: transitive + description: + name: device_calendar_plus_platform_interface + sha256: "0ce7511c094ca256831a48e16efe8f1e97e7bd00a5ff3936296ffd650a1d76b5" + url: "https://pub.dev" + source: hosted + version: "0.3.3" device_info_plus: dependency: "direct main" description: @@ -258,6 +306,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.2" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" equatable: dependency: "direct main" description: @@ -431,6 +487,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_callkit_incoming: + dependency: "direct main" + description: + name: flutter_callkit_incoming + sha256: "3589deb8b71e43f2d520a9c8a5240243f611062a8b246cdca4b1fda01fbbf9b8" + url: "https://pub.dev" + source: hosted + version: "3.0.0" flutter_hooks: dependency: transitive description: @@ -634,10 +698,10 @@ packages: dependency: "direct main" description: name: flutter_zoom_videosdk - sha256: "22731485fe48472a34ff0c7e787a382f5e1ec662fd89186e58e760974fc2a0cb" + sha256: "46a4dea664b1c969099328a499c198a1755adf9ac333dea28bea5187910b3bf9" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.1.10" fluttertoast: dependency: "direct main" description: @@ -894,6 +958,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + huawei_health: + dependency: "direct main" + description: + name: huawei_health + sha256: "52fb9990e1fc857e2fa1b1251dde63b2146086a13b2d9c50bdfc3c4f715c8a12" + url: "https://pub.dev" + source: hosted + version: "6.16.0+300" huawei_location: dependency: "direct main" description: @@ -923,10 +995,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02" + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" url: "https://pub.dev" source: hosted - version: "0.8.13+3" + version: "0.8.13+1" image_picker_for_web: dependency: transitive description: @@ -1075,18 +1147,18 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "1ee0e63fb8b5c6fa286796b5fb1570d256857c2f4a262127e728b36b80a570cf" + sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" url: "https://pub.dev" source: hosted - version: "1.0.53" + version: "1.0.52" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "1.6.0" local_auth_platform_interface: dependency: transitive description: @@ -1147,10 +1219,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.1" manage_calendar_events: dependency: "direct main" description: @@ -1407,6 +1479,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" posix: dependency: transitive description: @@ -1419,10 +1499,10 @@ packages: dependency: transitive description: name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "6.0.0" provider: dependency: "direct main" description: @@ -1463,6 +1543,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + scrollable_positioned_list: + dependency: "direct main" + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" share_plus: dependency: "direct main" description: @@ -1600,10 +1688,10 @@ packages: dependency: transitive description: name: sqflite_android - sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.1" sqflite_common: dependency: transitive description: @@ -1725,7 +1813,7 @@ packages: source: hosted version: "2.1.5" timezone: - dependency: transitive + dependency: "direct main" description: name: timezone sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 @@ -1752,10 +1840,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b" + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" url: "https://pub.dev" source: hosted - version: "6.3.22" + version: "6.3.20" url_launcher_ios: dependency: transitive description: @@ -1888,10 +1976,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.0.0" wakelock_plus: dependency: transitive description: @@ -1928,10 +2016,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "21507ea5a326ceeba4d29dea19e37d92d53d9959cfc746317b9f9f7a57418d87" + sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" url: "https://pub.dev" source: hosted - version: "4.10.3" + version: "4.10.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1944,10 +2032,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: fea63576b3b7e02b2df8b78ba92b48ed66caec2bb041e9a0b1cbd586d5d80bfd + sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f url: "https://pub.dev" source: hosted - version: "3.23.1" + version: "3.23.0" win32: dependency: transitive description: @@ -1981,5 +2069,5 @@ packages: source: hosted version: "6.6.1" sdks: - dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.32.0" From 1edeff439edd780c04b20c8f222f903e8d3847b6 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Thu, 15 Jan 2026 10:29:48 +0300 Subject: [PATCH 42/46] notifications page updates --- .../notification_details_page.dart | 284 ++++++++++++++++++ .../notifications_list_page.dart | 168 +++++++++-- 2 files changed, 435 insertions(+), 17 deletions(-) create mode 100644 lib/presentation/notifications/notification_details_page.dart diff --git a/lib/presentation/notifications/notification_details_page.dart b/lib/presentation/notifications/notification_details_page.dart new file mode 100644 index 0000000..a4aef02 --- /dev/null +++ b/lib/presentation/notifications/notification_details_page.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:intl/intl.dart'; +import 'package:share_plus/share_plus.dart'; + +class NotificationDetailsPage extends StatelessWidget { + final NotificationResponseModel notification; + + const NotificationDetailsPage({ + super.key, + required this.notification, + }); + + @override + Widget build(BuildContext context) { + // Debug logging + print('=== Notification Details ==='); + print('Message: ${notification.message}'); + print('MessageType: ${notification.messageType}'); + print('MessageTypeData: ${notification.messageTypeData}'); + print('VideoURL: ${notification.videoURL}'); + print('========================'); + + return CollapsingListView( + title: "Notification Details".needTranslation, + trailing: IconButton( + icon: Icon( + Icons.share_outlined, + size: 24.h, + color: AppColors.textColor, + ), + onPressed: () { + _shareNotification(); + }, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Notification content card + _buildNotificationCard(context), + SizedBox(height: 24.h), + ], + ).paddingSymmetrical(24.w, 0.h), + ), + ); + } + + Widget _buildNotificationCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Date and Time row + Row( + children: [ + // Time chip with clock icon + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.access_time, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatTime(notification.isSentOn).toText10( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ], + ), + ), + SizedBox(width: 8.w), + // Date chip with calendar icon + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatDate(notification.isSentOn).toText10( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.h), + + // Notification message + if (notification.message != null && notification.message!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Message'.needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + ), + SizedBox(height: 8.h), + notification.message!.toText16( + weight: FontWeight.w400, + color: AppColors.textColor, + maxlines: 100, + ), + SizedBox(height: 16.h), + ], + ), + + // Notification image (if MessageType is "image") + if (notification.messageType != null && + notification.messageType!.toLowerCase() == "image") + Builder( + builder: (context) { + // Try to get image URL from videoURL or messageTypeData + String? imageUrl; + + if (notification.videoURL != null && notification.videoURL!.isNotEmpty) { + imageUrl = notification.videoURL; + print('Image URL from videoURL: $imageUrl'); + } else if (notification.messageTypeData != null && notification.messageTypeData!.isNotEmpty) { + imageUrl = notification.messageTypeData; + print('Image URL from messageTypeData: $imageUrl'); + } + + if (imageUrl == null || imageUrl.isEmpty) { + print('No image URL found. videoURL: ${notification.videoURL}, messageTypeData: ${notification.messageTypeData}'); + return SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Attached Image'.needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + ), + SizedBox(height: 8.h), + ClipRRect( + borderRadius: BorderRadius.circular(12.h), + child: Image.network( + imageUrl, + width: double.infinity, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + print('Error loading image: $error'); + print('Image URL: $imageUrl'); + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.greyColor.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(12.h), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.broken_image_outlined, + size: 48.h, + color: AppColors.greyTextColor, + ), + SizedBox(height: 8.h), + 'Failed to load image'.needTranslation.toText12( + color: AppColors.greyTextColor, + ), + SizedBox(height: 4.h), + Text( + imageUrl!, + style: TextStyle(fontSize: 8, color: AppColors.greyTextColor), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + print('Image loaded successfully'); + return child; + } + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.greyColor.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(12.h), + ), + child: Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + ), + ), + SizedBox(height: 16.h), + ], + ); + }, + ), + + // Additional notification info + if (notification.notificationType != null && notification.notificationType!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Type'.needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + ), + SizedBox(height: 8.h), + notification.notificationType!.toText16( + weight: FontWeight.w400, + color: AppColors.textColor, + ), + ], + ), + ], + ), + ); + } + + void _shareNotification() async { + final String shareText = ''' +${notification.message ?? 'Notification'} + +Time: ${_formatTime(notification.isSentOn)} +Date: ${_formatDate(notification.isSentOn)} +${notification.notificationType != null ? '\nType: ${notification.notificationType}' : ''} + '''.trim(); + + await Share.share(shareText); + } + + String _formatTime(String? dateTimeString) { + if (dateTimeString == null || dateTimeString.isEmpty) return '--'; + try { + final dateTime = DateUtil.convertStringToDate(dateTimeString); + return DateFormat('hh:mm a').format(dateTime); + } catch (e) { + return '--'; + } + } + + String _formatDate(String? dateTimeString) { + if (dateTimeString == null || dateTimeString.isEmpty) return '--'; + try { + final dateTime = DateUtil.convertStringToDate(dateTimeString); + return DateFormat('dd MMM yyyy').format(dateTime); + } catch (e) { + return '--'; + } + } +} + diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 99d4270..650753c 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -1,15 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/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/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/notifications/notification_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:provider/provider.dart'; +import 'package:intl/intl.dart'; class NotificationsListPage extends StatelessWidget { const NotificationsListPage({super.key}); @@ -46,24 +50,134 @@ class NotificationsListPage extends StatelessWidget { child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - // "Notification Title".toText14(), - // SizedBox(height: 8.h), - Row( - children: [ - Expanded(child: notificationsVM.notificationsList[index].message!.toText16(isBold: notificationsVM.notificationsList[index].isRead ?? false)), - ], + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NotificationDetailsPage( + notification: notificationsVM.notificationsList[index], + ), ), - SizedBox(height: 12.h), - DateUtil.formatDateToDate(DateUtil.convertStringToDate(notificationsVM.notificationsList[index].isSentOn!), false).toText14(weight: FontWeight.w500), - 1.divider, - ], + ); + }, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + // Message row with red dot for unread + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: notificationsVM.notificationsList[index].message!.toText16( + isBold: (notificationsVM.notificationsList[index].isRead == false), + weight: (notificationsVM.notificationsList[index].isRead == false) + ? FontWeight.w600 + : FontWeight.w400, + ), + ), + SizedBox(width: 8.w), + // Red dot for unread notifications ONLY + if (notificationsVM.notificationsList[index].isRead == false) + Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + ), + ], + ), + SizedBox(height: 12.h), + // First row: Time and Date chips with arrow + Row( + children: [ + // Time chip with clock icon + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.access_time, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatTime(notificationsVM.notificationsList[index].isSentOn).toText10( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ], + ), + ), + SizedBox(width: 8.w), + // Date chip with calendar icon + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatDate(notificationsVM.notificationsList[index].isSentOn).toText10( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ], + ), + ), + Spacer(), + // Arrow icon + Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + width: 16.w, + height: 16.h, + iconColor: AppColors.greyTextColor, + ), + ], + ), + // Second row: Contains Image chip (if MessageType is "image") + if (notificationsVM.notificationsList[index].messageType != null && + notificationsVM.notificationsList[index].messageType!.toLowerCase() == "image") + Padding( + padding: EdgeInsets.only(top: 8.h), + child: Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + 'Contains Image'.toText10( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ], + ), + ), + ], + ), + ), + SizedBox(height: 16.h), + 1.divider, + ], + ), ), ), ), @@ -75,4 +189,24 @@ class NotificationsListPage extends StatelessWidget { ), ); } + + String _formatTime(String? dateTimeString) { + if (dateTimeString == null || dateTimeString.isEmpty) return '--'; + try { + final dateTime = DateUtil.convertStringToDate(dateTimeString); + return DateFormat('hh:mm a').format(dateTime); + } catch (e) { + return '--'; + } + } + + String _formatDate(String? dateTimeString) { + if (dateTimeString == null || dateTimeString.isEmpty) return '--'; + try { + final dateTime = DateUtil.convertStringToDate(dateTimeString); + return DateFormat('dd MMM yyyy').format(dateTime); + } catch (e) { + return '--'; + } + } } From 3ff9628cd3bf5405541bc898cdd57ed391045d05 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 15 Jan 2026 13:52:14 +0300 Subject: [PATCH 43/46] Translation updates --- assets/langs/ar-SA.json | 157 ++++++++- assets/langs/en-US.json | 160 ++++++++- lib/core/location_util.dart | 4 +- lib/core/utils/calendar_utils.dart | 6 +- .../widgets/hospital_selection.dart | 11 +- .../book_appointments_view_model.dart | 36 +- .../emergency_services_view_model.dart | 43 ++- .../utils/appointment_type.dart | 4 +- .../prescriptions_view_model.dart | 4 +- .../radiology/radiology_view_model.dart | 4 +- .../water_monitor_view_model.dart | 24 +- lib/generated/locale_keys.g.dart | 155 +++++++++ lib/presentation/home/landing_page.dart | 32 +- .../home/widgets/habib_wallet_card.dart | 4 +- .../home/widgets/large_service_card.dart | 2 +- .../home/widgets/welcome_widget.dart | 4 +- .../hhc_order_detail_page.dart | 7 +- .../home_health_care/hhc_procedures_page.dart | 33 +- .../hhc_selection_review_page.dart | 16 +- .../widgets/hhc_ui_selection_helper.dart | 4 +- .../insurance_approval_details_page.dart | 4 +- .../insurance/insurance_approvals_page.dart | 2 +- .../insurance/insurance_home_page.dart | 2 +- .../widgets/insurance_approval_card.dart | 2 +- .../insurance/widgets/insurance_history.dart | 2 +- .../insurance_update_details_card.dart | 2 +- .../widgets/patient_insurance_card.dart | 6 +- lib/presentation/lab/lab_order_by_test.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 2 +- .../LabResultByClinic.dart | 6 +- .../lab_result_via_clinic/LabResultList.dart | 5 +- .../lab_order_result_item.dart | 2 +- .../lab/lab_results/lab_result_details.dart | 21 +- .../eye_measurement_details_page.dart | 4 +- .../eye_measurements_appointments_page.dart | 4 +- .../medical_file/medical_file_page.dart | 111 ++++--- .../patient_sickleaves_list_page.dart | 4 +- .../medical_file/vaccine_list_page.dart | 6 +- .../medical_file_appointment_card.dart | 2 +- .../widgets/patient_sick_leave_card.dart | 4 +- .../medical_report_request_page.dart | 4 +- .../medical_report/medical_reports_page.dart | 16 +- .../widgets/patient_medical_report_card.dart | 2 +- .../monthly_report/monthly_report.dart | 9 +- .../monthly_reports/monthly_reports_page.dart | 310 ------------------ .../monthly_reports/user_agreement_page.dart | 117 ------- lib/presentation/my_family/my_family.dart | 8 +- .../my_family/widget/family_cards.dart | 26 +- .../my_family/widget/my_family_sheet.dart | 6 +- .../my_invoices/my_invoices_details_page.dart | 16 +- .../my_invoices/my_invoices_list.dart | 2 +- .../widgets/invoice_list_card.dart | 7 +- .../notifications_list_page.dart | 4 +- .../onboarding/onboarding_screen.dart | 16 +- lib/presentation/parking/paking_page.dart | 44 +-- lib/presentation/parking/parking_slot.dart | 20 +- ...scription_delivery_order_summary_page.dart | 4 +- ...rescription_delivery_orders_list_page.dart | 2 +- .../prescription_detail_page.dart | 14 +- .../prescription_reminder_view.dart | 2 +- .../prescriptions_list_page.dart | 4 +- .../profile_settings/profile_settings.dart | 52 ++- .../widgets/family_card_widget.dart | 12 +- .../radiology/radiology_orders_page.dart | 6 +- .../radiology/radiology_result_page.dart | 12 +- .../rate_appointment_clinic.dart | 14 +- .../rate_appointment_doctor.dart | 16 +- .../organ_selector_screen.dart | 14 +- .../possible_conditions_screen.dart | 6 +- .../symptoms_checker/risk_factors_screen.dart | 19 +- .../pages/age_selection_page.dart | 4 +- .../pages/gender_selection_page.dart | 8 +- .../pages/height_selection_page.dart | 4 +- .../pages/weight_selection_page.dart | 4 +- .../user_info_flow_manager.dart | 10 +- .../widgets/condition_card.dart | 6 +- .../widgets/selected_organs_section.dart | 6 +- 77 files changed, 887 insertions(+), 842 deletions(-) delete mode 100644 lib/presentation/monthly_reports/monthly_reports_page.dart delete mode 100644 lib/presentation/monthly_reports/user_agreement_page.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 19db982..f49873a 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1213,5 +1213,160 @@ "virtualTour": "جولة افتراضية", "carParking": "موقف السيارات", "latestNews": "آخر الأخبار", - "hmgContact": "اتصل بمجموعة الحبيب الطبية" + "hmgContact": "اتصل بمجموعة الحبيب الطبية", + "durationCannotExceed90": "لا يجوز أن تتجاوز المدة 90 دقيقة", + "unexpectedError": "حدث خطأ غير متوقع", + "gettingAmbulanceTransportOption": "جاري الحصول على خيارات نقل الإسعاف", + "fetchingAppointment": "جاري جلب الموعد", + "doYouWantToCancelTheRequest": "هل تريد إلغاء الطلب", + "cancellingRequest": "جاري إلغاء الطلب", + "fetchingTermsAndConditions": "جاري جلب الشروط والأحكام", + "selectLocationPrescriptionDelivery": "يرجى تحديد موقع توصيل الوصفة الطبية", + "noRadiologyOrders": "لم يتم العثور على أي طلبات تصوير شعاعي", + "ageIsRequired": "العمر مطلوب", + "invalidAge": "العمر غير صالح", + "ageMustBeBetween11And120": "يجب أن يكون العمر بين 11 و 120", + "heightIsRequired": "الطول مطلوب", + "invalidHeight": "الطول غير صالح", + "weightIsRequired": "الوزن مطلوب", + "invalidWeight": "الوزن غير صالح", + "timeToDrinkWater": "حان وقت شرب الماء! 💧", + "stayHydratedDrinkWater": "ابق رطبًا! اشرب {amount} مل من الماء.", + "visitPharmacyOnline": "زيارة الصيدلة على الانترنت", + "howAreYouFeelingToday": "كيف حالك اليوم؟", + "checkYourSymptomsWithScale": "تحقق من أعراضك باستخدام ذا المقياس", + "checkYourSymptoms": "تحقق من أعراضك", + "noUpcomingAppointmentPleaseBook": "ليس لديك أي مواعيد قادمة. يرجى حجز موعد", + "youHaveEROnlineCheckInRequest": "لديك طلب تسجيل وصول عبر الإنترنت للطوارئ", + "quickLinks": "روابط سريعة", + "viewMedicalFileLandingPage": "عرض الملف الطبي", + "immediateLiveCareRequest": "طلب LiveCare الفوري", + "yourTurnIsAfterPatients": "دورك بعد {count} مريض.", + "dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.", + "hhcOrders": "أوامر الرعاية الصحية المنزلية", + "requestedServices": "الخدمات المطلوبة", + "selectServices": "اختر الخدمات", + "selectedServices": "الخدمات المختارة", + "createNewRequest": "إنشاء طلب جديد", + "youHaveNoPendingRequests": "ليس لديك أي طلبات معلقة.", + "noInsuranceDataFound": "لم يتم العثور على بيانات التأمين...", + "noInsuranceUpdateRequest": "لم يتم العثور على أي طلبات لتحديث بيانات التأمين.", + "policyNumberInsurancePage": "الوثيقة: {number}", + "insuranceExpired": "التأمين منتهي الصلاحية", + "insuranceActive": "التأمين نشط", + "patientCardID": "رقم بطاقة المريض: {id}", + "noInsuranceApprovals": "لم تحصل على أي موافقات تأمينية حتى الآن.", + "noInsuranceWithHMG": "ليس لديك تأمين مسجل لدى مجموعة حبيب الطبية.", + "referenceRange": "النطاق المرجعي", + "downloadReport": "تنزيل التقرير", + "generatingReport": "جارٍ إنشاء التقرير، يرجى الانتظار...", + "noLabResults": "ليس لديك أي نتائج مختبرية حتى الآن.", + "labResultDetails": "تفاصيل نتائج المختبر", + "resultOf": "نتيجة", + "whatIsThisResult": "ما هي هذه النتيجة؟", + "lastTested": "آخر اختبار", + "byVisit": "حسب الزيارة", + "byTest": "حسب التحليل", + "results": "نتائج", + "viewResults": "عرض النتائج", + "rebook": "إعادة الحجز", + "noOphthalmologyAppointments": "لم يتم العثور على أي مواعيد في قسم طب العيون...", + "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", + "appointmentsAndVisits": "المواعيد والزيارات", + "labAndRadiology": "المختبر والأشعة", + "activeMedicationsAndPrescriptions": "الأدوية النشطة والوصفات الطبية", + "allPrescriptions": "جميع الوصفات", + "allMedications": "جميع الأدوية", + "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", + "youDontHaveAnyCompletedVisitsYet": "ليس لديك أي زيارات مكتملة بعد", + "others": "أخرى", + "allergyInfo": "معلومات الحساسية", + "vaccineInfo": "معلومات اللقاحات", + "updateInsuranceInfo": "تحديث التأمين", + "myInvoicesList": "قائمة فواتيري", + "ancillaryOrdersList": "قائمة الطلبات المساعدة", + "youDontHaveAnySickLeavesYet": "ليس لديك أي إجازات مرضية بعد.", + "medicalReports": "التقارير الطبية", + "sickLeaveReport": "تقرير الإجازة المرضية", + "weightTracker": "متتبع الوزن", + "askYourDoctor": "اسأل طبيبك", + "internetPairing": "الاقتران بالإنترنت", + "requested": "مطلوب", + "youDontHaveAnyMedicalReportsYet": "ليس لديك أي تقارير طبية بعد.", + "requestMedicalReport": "طلب تقرير طبي", + "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "ليس لديك أي مواعيد لطلب تقرير طبي.", + "areYouSureYouWantToRequestMedicalReport": "هل أنت متأكد أنك تريد طلب تقرير طبي لهذا الموعد؟", + "yourMedicalReportRequestSubmittedSuccessfully": "تم إرسال طلب التقرير الطبي بنجاح.", + "monthlyHealthSummaryReportDisclaimer": "يعكس تقرير الملخص الصحي الشهري هذا المؤشرات الصحية ونتائج التحليل لأحدث الزيارات. يرجى ملاحظة أن هذا سيتم إرساله تلقائيًا من النظام ولا يعتبر تقريرًا رسميًا لذا لا ينبغي اتخاذ أي قرار طبي بناءً عليه", + "updatingMonthlyReportStatus": "جاري تحديث حالة التقرير الشهري...", + "monthlyReportStatusUpdatedSuccessfully": "تم تحديث حالة التقرير الشهري بنجاح", + "whoCanViewMyMedicalFile": "من يمكنه عرض ملفي الطبي؟", + "acceptedYourRequestToBeYourFamilyMember": "{status} طلبك لتكون فردًا من عائلتك", + "canViewYourFile": "يمكنه عرض ملفك", + "hasARequestPendingToBeYourFamilyMember": "لديه طلب {status} ليكون فردًا من عائلتك", + "wantsToAddYouAsTheirFamilyMember": "يريد إضافتك كفرد من عائلته", + "rejectedYourRequestToBeYourFamilyMember": "{status} طلبك لتكون فردًا من عائلتك", + "rejectedYourFamilyMemberRequest": "{status} طلب فرد عائلتك", + "notAvailable": "غير متاح", + "selectAProfile": "الرجاء تحديد ملف تعريف", + "switchFamilyFile": "قم بالتبديل من قائمة الملفات الطبية أدناه", + "medicalFiles": "الملفات الطبية", + "addANewFamilyMember": "إضافة فرد جديد من العائلة", + "viewInvoiceDetails": "عرض تفاصيل الفاتورة", + "outPatient": "مريض خارجي", + "invoiceDetails": "تفاصيل الفاتورة", + "sendingEmailPleaseWait": "جاري إرسال ال��ريد الإلكتروني، يرجى الانتظار...", + "emailSentSuccessfullyMessage": "تم إرسال البريد الإلكتروني بنجاح.", + "discount": "خصم", + "paid": "مدفوع", + "fetchingInvoiceDetails": "جارٍ جلب تفاصيل الفاتورة، يرجى الانتظار...", + "scanQRCode": "مسح رمز الاستجابة السريعة", + "parkingSlotDetails": "تفاصيل موقف السيارة", + "slotNumber": "رقم الموقف: {code}", + "basement": "الطابق: {description}", + "parkingDate": "التاريخ: {date}", + "parkedSince": "متوقف منذ: {time}", + "resetDirection": "إعادة تعيين الاتجاه", + "noPrescriptionOrdersYet": "ليس لديك أي طلبات وصفات طبية حتى الآن.", + "fetchingPrescriptionPDFPleaseWait": "جاري جلب ملف الوصفة الطبية، يرجى الانتظار...", + "ratingValue": "التقييم: {rating}", + "downloadPrescription": "تحميل الوصفة الطبية", + "fetchingPrescriptionDetails": "جاري جلب تفاصيل الوصفة الطبية...", + "switchBackFamilyFile": "العودة إلى ملف العائلة", + "profileAndSettings": "الملف الشخصي والإعدادات", + "quickActions": "إجراءات سريعة", + "notificationsSettings": "إعدادات الإشعارات", + "touchIDFaceIDServices": "خدمات Touch ID / Face ID", + "personalInformation": "المعلومات الشخصية", + "updateEmailAddress": "تحديث عنوان البريد الإلكتروني", + "helpAndSupport": "المساعدة والدعم", + "permissionsProfile": "الأذونات", + "privacyPolicy": "سياسة الخصوصية", + "deactivateAccount": "إلغاء تنشيط الحساب", + "ageYearsOld": "{age} {yearsOld}", + "youDontHaveRadiologyOrders": "ليس لديك أي نتائج للأشعة حتى الآن.", + "radiologyResult": "نتيجة الأشعة", + "viewRadiologyImage": "عرض صورة الأشعة", + "rateClinic": "تقييم العيادة", + "back": "رجوع", + "rateDoctor": "تقييم الطبيب", + "howWasYourLastVisitWithDoctor": "كيف كانت زيارتك الأخيرة مع الطبيب؟", + "dateOfBirthSymptoms": "ما هو تاريخ ميلادك؟", + "genderSymptoms": "ما هو جنسك؟", + "heightSymptoms": "كم طولك؟", + "weightSymptoms": "ما هو وزنك؟", + "femaleGender": "أنثى", + "previous": "سابق", + "selectedOrgans": "الهيئات المختارة", + "noOrgansSelected": "لم يتم تحديد أي أعضاء بعد", + "organSelector": "محدد الأعضاء", + "noPredictionsAvailable": "لا توجد تنبؤات متاحة", + "areYouSureYouWantToRestartOrganSelection": "هل أنت متأكد أنك تريد إعادة تشغيل اختيار الأعضاء؟", + "possibleConditions": "الحالات المحتملة", + "pleaseSelectAtLeastOneRiskBeforeProceeding": "يرجى اختيار عامل خطر واحد على الأقل قبل المتابعة", + "aboveYouSeeCommonRiskFactors": "أعلاه ترى عوامل الخطر الأكثر شيوعًا. على الرغم من أن /diagnosis قد تعيد أسئلة حول عوامل الخطر، ", + "readMore": "اقرأ المزيد", + "riskFactors": "عوامل الخطر", + "noRiskFactorsFound": "لم يتم العثور على عوامل خطر", + "basedOnYourSelectedSymptomsNoRiskFactors": "بناءً على الأعراض المحددة، لم يتم تحديد عوامل خطر إضافية." } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index ee489f5..c4b6c19 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -889,7 +889,6 @@ "pickADate": "Pick a Date", "confirmingAppointmentPleaseWait": "Confirming Appointment, Please Wait...", "appointmentConfirmedSuccessfully": "Appointment Confirmed Successfully", - "appointmentPayment": "Appointment Payment", "checkingPaymentStatusPleaseWait": "Checking payment status, Please wait...", "paymentFailedPleaseTryAgain": "Payment Failed! Please try again.", @@ -1207,5 +1206,160 @@ "virtualTour": "Virtual Tour", "carParking": "Car Parking", "latestNews": "Latest News", - "hmgContact": "HMG Contact" -} \ No newline at end of file + "hmgContact": "HMG Contact", + "durationCannotExceed90": "Duration can not exceed 90 mins", + "unexpectedError": "Unexpected Error Occurred", + "gettingAmbulanceTransportOption": "Getting Ambulance Transport Option", + "fetchingAppointment": "Fetching Appointment", + "doYouWantToCancelTheRequest": "Do you want to cancel the request", + "cancellingRequest": "Cancelling request", + "fetchingTermsAndConditions": "Fetching Terms And Conditions", + "selectLocationPrescriptionDelivery": "Please select the location for prescription delivery", + "noRadiologyOrders": "No Radiology Orders Found", + "ageIsRequired": "Age is required", + "invalidAge": "Invalid age", + "ageMustBeBetween11And120": "Age must be between 11 and 120", + "heightIsRequired": "Height is required", + "invalidHeight": "Invalid height", + "weightIsRequired": "Weight is required", + "invalidWeight": "Invalid weight", + "timeToDrinkWater": "Time to Drink Water! 💧", + "stayHydratedDrinkWater": "Stay hydrated! Drink {amount}ml of water.", + "visitPharmacyOnline": "Visit Pharmacy Online", + "howAreYouFeelingToday": "How are you feeling today?", + "checkYourSymptomsWithScale": "Check your symptoms with this scale", + "checkYourSymptoms": "Check your symptoms", + "noUpcomingAppointmentPleaseBook": "You do not have any upcoming appointment. Please book an appointment", + "youHaveEROnlineCheckInRequest": "You have ER Online Check-In Request", + "quickLinks": "Quick Links", + "viewMedicalFileLandingPage": "View medical file", + "immediateLiveCareRequest": "Immediate LiveCare Request", + "yourTurnIsAfterPatients": "Your turn is after {count} patients.", + "dontHaveHHCOrders": "You don't have any Home Health Care orders yet.", + "hhcOrders": "HHC Orders", + "requestedServices": "Requested Services", + "selectServices": "Select Services", + "selectedServices": "Selected Services", + "createNewRequest": "Create new request", + "youHaveNoPendingRequests": "You have no pending requests.", + "noInsuranceDataFound": "No insurance data found...", + "noInsuranceUpdateRequest": "No insurance update requests found.", + "policyNumberInsurancePage": "Policy: {number}", + "insuranceExpired": "Insurance Expired", + "insuranceActive": "Insurance Active", + "patientCardID": "Patient Card ID: {id}", + "noInsuranceApprovals": "You don't have any insurance approvals yet.", + "noInsuranceWithHMG": "You don't have insurance registered with HMG.", + "referenceRange": "Reference Range", + "downloadReport": "Download report", + "generatingReport": "Generating report, Please wait...", + "noLabResults": "You don't have any lab results yet.", + "labResultDetails": "Lab Result Details", + "resultOf": "Result of", + "whatIsThisResult": "What is this result?", + "lastTested": "Last Tested", + "byVisit": "By Visit", + "byTest": "By Test", + "results": "results", + "viewResults": "View Results", + "rebook": "Rebook", + "noOphthalmologyAppointments": "No Ophthalmology appointments found...", + "noVitalSignsRecordedYet": "No vital signs recorded yet", + "appointmentsAndVisits": "Appointments & visits", + "labAndRadiology": "Lab & Radiology", + "activeMedicationsAndPrescriptions": "Active Medications & Prescriptions", + "allPrescriptions": "All Prescriptions", + "allMedications": "All Medications", + "youDontHaveAnyPrescriptionsYet": "You don't have any prescriptions yet.", + "youDontHaveAnyCompletedVisitsYet": "You don't have any completed visits yet", + "others": "Others", + "allergyInfo": "Allergy Info", + "vaccineInfo": "Vaccine Info", + "updateInsuranceInfo": "Update Insurance", + "myInvoicesList": "My Invoices List", + "ancillaryOrdersList": "Ancillary Orders List", + "youDontHaveAnySickLeavesYet": "You don't have any sick leaves yet.", + "medicalReports": "Medical Reports", + "sickLeaveReport": "Sick Leave Report", + "weightTracker": "Weight Tracker", + "askYourDoctor": "Ask Your Doctor", + "internetPairing": "Internet Pairing", + "requested": "Requested", + "youDontHaveAnyMedicalReportsYet": "You don't have any medical reports yet.", + "requestMedicalReport": "Request medical report", + "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.", + "areYouSureYouWantToRequestMedicalReport": "Are you sure you want to request a medical report for this appointment?", + "yourMedicalReportRequestSubmittedSuccessfully": "Your medical report request has been successfully submitted.", + "monthlyHealthSummaryReportDisclaimer": "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as a official report so no medical decision should be taken based on it", + "updatingMonthlyReportStatus": "Updating Monthly Report Status...", + "monthlyReportStatusUpdatedSuccessfully": "Monthly Report Status Updated Successfully", + "whoCanViewMyMedicalFile": "Who can view my medical file?", + "acceptedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", + "canViewYourFile": "can view your file", + "hasARequestPendingToBeYourFamilyMember": "has a request {status} to be your family member", + "wantsToAddYouAsTheirFamilyMember": "wants to add you as their family member", + "rejectedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", + "rejectedYourFamilyMemberRequest": "{status} your family member request", + "notAvailable": "N/A", + "selectAProfile": "Please select a profile", + "switchFamilyFile": "Switch from the below list of medical file", + "medicalFiles": "Medical Files", + "addANewFamilyMember": "Add a new family member", + "viewInvoiceDetails": "View invoice details", + "outPatient": "OutPatient", + "invoiceDetails": "Invoice Details", + "sendingEmailPleaseWait": "Sending email, Please wait...", + "emailSentSuccessfullyMessage": "Email sent successfully.", + "discount": "Discount", + "paid": "Paid", + "fetchingInvoiceDetails": "Fetching invoice details, Please wait...", + "scanQRCode": "Scan QR code", + "parkingSlotDetails": "Parking Slot Details", + "slotNumber": "Slot: {code}", + "basement": "Basement: {description}", + "parkingDate": "Date: {date}", + "parkedSince": "Parked Since: {time}", + "resetDirection": "Reset Direction", + "noPrescriptionOrdersYet": "You don't have any prescription orders yet.", + "fetchingPrescriptionPDFPleaseWait": "Fetching prescription PDF, Please wait...", + "ratingValue": "Rating: {rating}", + "downloadPrescription": "Download Prescription", + "fetchingPrescriptionDetails": "Fetching prescription details...", + "switchBackFamilyFile": "Switch Back To Family File", + "profileAndSettings": "Profile & Settings", + "quickActions": "Quick Actions", + "notificationsSettings": "Notifications Settings", + "touchIDFaceIDServices": "Touch ID / Face ID Services", + "personalInformation": "Personal Information", + "updateEmailAddress": "Update Email Address", + "helpAndSupport": "Help & Support", + "permissionsProfile": "Permissions", + "privacyPolicy": "Privacy Policy", + "deactivateAccount": "Deactivate account", + "ageYearsOld": "{age} {yearsOld}", + "youDontHaveRadiologyOrders": "You don't have any radiology results yet.", + "radiologyResult": "Radiology Result", + "viewRadiologyImage": "View Radiology Image", + "rateClinic": "Rate Clinic", + "back": "Back", + "rateDoctor": "Rate Doctor", + "howWasYourLastVisitWithDoctor": "How was your last visit with doctor?", + "dateOfBirthSymptoms": "What is your Date of Birth?", + "genderSymptoms": "What is your gender?", + "heightSymptoms": "How tall are you?", + "weightSymptoms": "What is your weight?", + "femaleGender": "Female", + "previous": "Previous", + "selectedOrgans": "Selected Organs", + "noOrgansSelected": "No organs selected yet", + "organSelector": "Organ Selector", + "noPredictionsAvailable": "No Predictions available", + "areYouSureYouWantToRestartOrganSelection": "Are you sure you want to restart the organ selection?", + "possibleConditions": "Possible Conditions", + "pleaseSelectAtLeastOneRiskBeforeProceeding": "Please select at least one risk before proceeding", + "aboveYouSeeCommonRiskFactors": "Above you see the most common risk factors. Although /diagnosis may return questions about risk factors, ", + "readMore": "Read more", + "riskFactors": "Risk Factors", + "noRiskFactorsFound": "No risk factors found", + "basedOnYourSelectedSymptomsNoRiskFactors": "Based on your selected symptoms, no additional risk factors were identified." +} diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index 9dcdbb5..cf26d24 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -104,7 +104,7 @@ class LocationUtils { title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see better results".needTranslation, + loadingText: "Please grant location permission from app settings to see better results", isShowActionButtons: true, onCancelTap: () { navigationService.pop(); @@ -265,7 +265,7 @@ class LocationUtils { title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see better results".needTranslation, + loadingText: "Please grant location permission from app settings to see better results", isShowActionButtons: true, onCancelTap: () { navigationService.pop(); diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 8c0db18..8b21111 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -215,14 +215,14 @@ showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorNa Future _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async { - showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder".needTranslation, child: PrescriptionReminderView( + showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder", child: PrescriptionReminderView( setReminder: (int value) async { if (!isMultiAllowed!) { if (onMultiDateSuccess == null) { CalendarUtils calendarUtils = await CalendarUtils.getInstance(); await calendarUtils.createOrUpdateEvent( - title: title ?? "You have appointment with Dr. ".needTranslation + doctorName, - description: description ?? "At " + appoDateFormatted + " " + appoTimeFormatted, + title: title ?? "You have appointment with Dr. $doctorName", + description: description ?? "At $appoDateFormatted $appoTimeFormatted", scheduleDateTime: dateTime, eventId: eventId, location: ''); diff --git a/lib/features/blood_donation/widgets/hospital_selection.dart b/lib/features/blood_donation/widgets/hospital_selection.dart index 288ac34..c6065ae 100644 --- a/lib/features/blood_donation/widgets/hospital_selection.dart +++ b/lib/features/blood_donation/widgets/hospital_selection.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -8,6 +9,7 @@ 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/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:provider/provider.dart'; @@ -23,14 +25,7 @@ class HospitalBottomSheetBodySelection extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Please select the hospital you want to make an appointment.".needTranslation, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor, - ), - ), + LocaleKeys.selectHospital.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), SizedBox(height: 16.h), SizedBox( height: MediaQuery.sizeOf(context).height * .4, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 3f5517a..664f244 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -473,7 +473,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError!("No doctors found for the search criteria".needTranslation); + onError!(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -501,7 +501,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { isDoctorsListLoading = false; - if (onError != null) onError("No doctors found for the search criteria".needTranslation); + if (onError != null) onError(LocaleKeys.noDoctorFound.tr()); notifyListeners(); }, @@ -533,7 +533,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { isDoctorsListLoading = false; - if (onError != null) onError("No doctors found for the search criteria".needTranslation); + if (onError != null) onError(LocaleKeys.noDoctorFound.tr()); notifyListeners(); }, @@ -569,7 +569,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError?.call("No doctors found for the search criteria".needTranslation); + onError?.call(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) async { if (apiResponse.messageStatus == 2) { @@ -784,7 +784,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); showCommonBottomSheet(navigationService.navigatorKey.currentContext!, - child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation), + child: Utils.getLoadingWidget(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, @@ -794,7 +794,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr()); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { @@ -880,7 +880,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); showCommonBottomSheet(navigationService.navigatorKey.currentContext!, - child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation), + child: Utils.getLoadingWidget(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, @@ -890,7 +890,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr()); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { @@ -1204,7 +1204,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError!("No doctors found for the search criteria...".needTranslation); + onError!(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -1291,18 +1291,18 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } else { - if (this.duration == 90) { - dialogService.showErrorBottomSheet( - message: "Duration can not exceed 90 min".needTranslation, - ); - return; - } + // if (this.duration == 90) { + // dialogService.showErrorBottomSheet( + // message: "Duration can not exceed 90 min".needTranslation, + // ); + // return; + // } selectedBodyPartList.add(part); var duration = getDuration(); if (duration > 90) { selectedBodyPartList.remove(part); dialogService.showErrorBottomSheet( - message: "Duration Exceeds 90 min".needTranslation, + message: LocaleKeys.durationCannotExceed90.tr(), ); return; } @@ -1336,7 +1336,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError!("Invalid verification point scanned.".needTranslation); + onError!("Invalid verification point scanned."); }, (apiResponse) { // if (apiResponse.data['returnValue'] == 0) { @@ -1410,7 +1410,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } else if (apiResponse.messageStatus == 1) { if (apiResponse.data == null || apiResponse.data!.isEmpty) { - onError!("Unexpected Error Occurred".needTranslation); + onError!(LocaleKeys.unexpectedError.tr()); return; } notifyListeners(); diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 400eb04..ec21179 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -32,6 +33,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/facility_sel import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/terms_and_condition.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; @@ -152,7 +154,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { print("the app state is ${appState.isAuthenticated}"); if (!appState.isAuthenticated) { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); getIt().onLoginPressed(); @@ -196,7 +198,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void filterErList(String query) { - print("the query is $query"); if (query.isEmpty) { nearestERFilteredList = nearestERList; } else { @@ -277,7 +278,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { flushData(); selectedFacility = FacilitySelection.ALL; - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated) { locationUtils!.getLocation( isShowConfirmDialog: true, @@ -289,7 +289,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { }); } else { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); navServices.pushAndReplace(AppRoutes.loginScreen); @@ -311,7 +311,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { void updateBottomSheetState(BottomSheetType sheetType) { if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) { - dialogService.showErrorBottomSheet(message: "Kindly Select Hospital".needTranslation); + dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr()); return; } bottomSheetType = sheetType; @@ -481,21 +481,18 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationOption() async { //handle the cache if the data is present then dont fetch it in the authenticated lifecycle - - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated == false) { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); - print("inside the ok button"); getIt().onLoginPressed(); }); return; } int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.gettingAmbulanceTransportOption.tr()); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -514,7 +511,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationMethods() async { int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.gettingAmbulanceTransportOption.tr()); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -703,7 +700,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future getAppointments() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointment.tr()); var result = await appointmentRepo.getPatientAppointments(isActiveAppointment: true, isArrivedAppointments: false); LoaderBottomSheet.hideLoader(); @@ -860,10 +857,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future cancelOrder(AmbulanceRequestOrdersModel? order, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the request".needTranslation, + message: LocaleKeys.doYouWantToCancelTheRequest.tr(), onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingRequest.tr()); var response = await emergencyServicesRepo.cancelOrder(order?.iD, appState.getAuthenticatedUser()?.patientId ?? 0); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -968,10 +965,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { FutureOr cancelRRTOrder(int? orderID, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the request".needTranslation, + message: LocaleKeys.doYouWantToCancelTheRequest.tr(), onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingRequest.tr()); var response = await emergencyServicesRepo.cancelRRTOrder(orderID); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -1001,11 +998,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void openRRT() { - print("the app state is ${appState.isAuthenticated}"); if (appState.isAuthenticated) { if (agreedToTermsAndCondition == false) { dialogService.showErrorBottomSheet( - message: "You Need To Agree To Terms And Conditions".needTranslation, + message: LocaleKeys.pleaseAcceptTermsConditions.tr(), onOkPressed: () { if (navServices.context == null) return; showCommonBottomSheetWithoutHeight( @@ -1042,9 +1038,9 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool result = await navServices.push( CustomPageRoute( page: MapUtilityScreen( - confirmButtonString: "Submit Request".needTranslation, - titleString: "Select Location".needTranslation, - subTitleString: "Please select the location".needTranslation, + confirmButtonString: LocaleKeys.submitRequest.tr(), + titleString: LocaleKeys.selectLocation.tr(), + subTitleString: LocaleKeys.pleaseSelectTheLocation.tr(), isGmsAvailable: appState.isGMSAvailable, ), direction: AxisDirection.down), @@ -1059,7 +1055,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { }); } else { dialogService.showErrorBottomSheet( - message: "You Need To Login First To Continue".needTranslation, + message: LocaleKeys.loginToUseService.tr(), onOkPressed: () { navServices.pop(); getIt().onLoginPressed(); @@ -1072,12 +1068,11 @@ class EmergencyServicesViewModel extends ChangeNotifier { } FutureOr getTermsAndConditions() async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Terms And Conditions".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingTermsAndConditions.tr()); var response = await emergencyServicesRepo.getTermsAndCondition(); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { termsAndConditions = success.data; - print("the response terms are $termsAndConditions"); notifyListeners(); navServices.push( CustomPageRoute(page: TermsAndCondition(termsAndCondition: success.data ?? ""), direction: AxisDirection.down), diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index abc23dc..aa2ef38 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -84,7 +84,7 @@ class AppointmentType { static String getNextActionText(nextAction) { switch (nextAction) { case 0: - return "No Action".needTranslation; + return LocaleKeys.upcomingNoAction.tr(); case 10: return LocaleKeys.confirm.tr(); case 15: @@ -96,7 +96,7 @@ class AppointmentType { case 90: return LocaleKeys.checkinOption.tr(); default: - return "No Action".needTranslation; + return LocaleKeys.upcomingNoAction.tr(); } } diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ff86406..3f9af34 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -245,8 +245,8 @@ class PrescriptionsViewModel extends ChangeNotifier { CustomPageRoute( page: MapUtilityScreen( confirmButtonString: LocaleKeys.next.tr(), - titleString: "Select Location".needTranslation, - subTitleString: "Please select the location for prescription delivery".needTranslation, + titleString: LocaleKeys.selectLocation.tr(), + subTitleString: LocaleKeys.selectLocationPrescriptionDelivery.tr(), isGmsAvailable: getIt.get().isGMSAvailable, ), direction: AxisDirection.down), diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 986945e..46a27bf 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -97,7 +99,7 @@ class RadiologyViewModel extends ChangeNotifier { ); } else { if (onError != null) { - onError("No Radiology Orders Found".needTranslation); + onError(LocaleKeys.noRadiologyOrders.tr()); } } } diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index d82712a..1ef2ef6 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -14,6 +15,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/models/update_user_de import 'package:hmg_patient_app_new/features/water_monitor/models/user_progress_models.dart'; import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -598,36 +600,36 @@ class WaterMonitorViewModel extends ChangeNotifier { String? validateAge() { if (ageController.text.trim().isEmpty) { - return 'Age is required'.needTranslation; + return LocaleKeys.ageIsRequired.tr(); } final age = int.tryParse(ageController.text.trim()); if (age == null) { - return 'Invalid age'.needTranslation; + return LocaleKeys.invalidAge.tr(); } if (age < 11 || age > 120) { - return 'Age must be between 11 and 120'.needTranslation; + return LocaleKeys.ageMustBeBetween11And120.tr(); } return null; } String? validateHeight() { if (heightController.text.trim().isEmpty) { - return 'Height is required'.needTranslation; + return LocaleKeys.heightIsRequired.tr(); } final height = double.tryParse(heightController.text.trim()); if (height == null || height <= 0) { - return 'Invalid height'.needTranslation; + return LocaleKeys.invalidHeight.tr(); } return null; } String? validateWeight() { if (weightController.text.trim().isEmpty) { - return 'Weight is required'.needTranslation; + return LocaleKeys.weightIsRequired.tr(); } final weight = double.tryParse(weightController.text.trim()); if (weight == null || weight <= 0) { - return 'Invalid weight'.needTranslation; + return LocaleKeys.invalidWeight.tr(); } return null; } @@ -1212,8 +1214,8 @@ class WaterMonitorViewModel extends ChangeNotifier { // Schedule water reminders await notificationService.scheduleWaterReminders( reminderTimes: reminderTimes, - title: 'Time to Drink Water! 💧'.needTranslation, - body: 'Stay hydrated! Drink ${selectedCupCapacityMl}ml of water.'.needTranslation, + title: LocaleKeys.timeToDrinkWater.tr(), + body: LocaleKeys.stayHydratedDrinkWater.tr(namedArgs: {'amount': selectedCupCapacityMl.toString()}), ); // Save reminder enabled state to cache @@ -1334,8 +1336,8 @@ class WaterMonitorViewModel extends ChangeNotifier { await notificationService.scheduleNotification( id: 9999, // Use a unique ID for test notifications - title: 'Time to Drink Water! 💧'.needTranslation, - body: 'Stay hydrated! Drink ${selectedCupCapacityMl}ml of water.'.needTranslation, + title: LocaleKeys.timeToDrinkWater.tr(), + body: LocaleKeys.stayHydratedDrinkWater.tr(namedArgs: {'amount': selectedCupCapacityMl.toString()}), scheduledDate: scheduledTime, payload: 'test_notification', ); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 689587c..a3989ad 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1208,5 +1208,160 @@ abstract class LocaleKeys { static const carParking = 'carParking'; static const latestNews = 'latestNews'; static const hmgContact = 'hmgContact'; + static const durationCannotExceed90 = 'durationCannotExceed90'; + static const unexpectedError = 'unexpectedError'; + static const gettingAmbulanceTransportOption = 'gettingAmbulanceTransportOption'; + static const fetchingAppointment = 'fetchingAppointment'; + static const doYouWantToCancelTheRequest = 'doYouWantToCancelTheRequest'; + static const cancellingRequest = 'cancellingRequest'; + static const fetchingTermsAndConditions = 'fetchingTermsAndConditions'; + static const selectLocationPrescriptionDelivery = 'selectLocationPrescriptionDelivery'; + static const noRadiologyOrders = 'noRadiologyOrders'; + static const ageIsRequired = 'ageIsRequired'; + static const invalidAge = 'invalidAge'; + static const ageMustBeBetween11And120 = 'ageMustBeBetween11And120'; + static const heightIsRequired = 'heightIsRequired'; + static const invalidHeight = 'invalidHeight'; + static const weightIsRequired = 'weightIsRequired'; + static const invalidWeight = 'invalidWeight'; + static const timeToDrinkWater = 'timeToDrinkWater'; + static const stayHydratedDrinkWater = 'stayHydratedDrinkWater'; + static const visitPharmacyOnline = 'visitPharmacyOnline'; + static const howAreYouFeelingToday = 'howAreYouFeelingToday'; + static const checkYourSymptomsWithScale = 'checkYourSymptomsWithScale'; + static const checkYourSymptoms = 'checkYourSymptoms'; + static const noUpcomingAppointmentPleaseBook = 'noUpcomingAppointmentPleaseBook'; + static const youHaveEROnlineCheckInRequest = 'youHaveEROnlineCheckInRequest'; + static const quickLinks = 'quickLinks'; + static const viewMedicalFileLandingPage = 'viewMedicalFileLandingPage'; + static const immediateLiveCareRequest = 'immediateLiveCareRequest'; + static const yourTurnIsAfterPatients = 'yourTurnIsAfterPatients'; + static const dontHaveHHCOrders = 'dontHaveHHCOrders'; + static const hhcOrders = 'hhcOrders'; + static const requestedServices = 'requestedServices'; + static const selectServices = 'selectServices'; + static const selectedServices = 'selectedServices'; + static const createNewRequest = 'createNewRequest'; + static const youHaveNoPendingRequests = 'youHaveNoPendingRequests'; + static const noInsuranceDataFound = 'noInsuranceDataFound'; + static const noInsuranceUpdateRequest = 'noInsuranceUpdateRequest'; + static const policyNumberInsurancePage = 'policyNumberInsurancePage'; + static const insuranceExpired = 'insuranceExpired'; + static const insuranceActive = 'insuranceActive'; + static const patientCardID = 'patientCardID'; + static const noInsuranceApprovals = 'noInsuranceApprovals'; + static const noInsuranceWithHMG = 'noInsuranceWithHMG'; + static const referenceRange = 'referenceRange'; + static const downloadReport = 'downloadReport'; + static const generatingReport = 'generatingReport'; + static const noLabResults = 'noLabResults'; + static const labResultDetails = 'labResultDetails'; + static const resultOf = 'resultOf'; + static const whatIsThisResult = 'whatIsThisResult'; + static const lastTested = 'lastTested'; + static const byVisit = 'byVisit'; + static const byTest = 'byTest'; + static const results = 'results'; + static const viewResults = 'viewResults'; + static const rebook = 'rebook'; + static const noOphthalmologyAppointments = 'noOphthalmologyAppointments'; + static const noVitalSignsRecordedYet = 'noVitalSignsRecordedYet'; + static const appointmentsAndVisits = 'appointmentsAndVisits'; + static const labAndRadiology = 'labAndRadiology'; + static const activeMedicationsAndPrescriptions = 'activeMedicationsAndPrescriptions'; + static const allPrescriptions = 'allPrescriptions'; + static const allMedications = 'allMedications'; + static const youDontHaveAnyPrescriptionsYet = 'youDontHaveAnyPrescriptionsYet'; + static const youDontHaveAnyCompletedVisitsYet = 'youDontHaveAnyCompletedVisitsYet'; + static const others = 'others'; + static const allergyInfo = 'allergyInfo'; + static const vaccineInfo = 'vaccineInfo'; + static const updateInsuranceInfo = 'updateInsuranceInfo'; + static const myInvoicesList = 'myInvoicesList'; + static const ancillaryOrdersList = 'ancillaryOrdersList'; + static const youDontHaveAnySickLeavesYet = 'youDontHaveAnySickLeavesYet'; + static const medicalReports = 'medicalReports'; + static const sickLeaveReport = 'sickLeaveReport'; + static const weightTracker = 'weightTracker'; + static const askYourDoctor = 'askYourDoctor'; + static const internetPairing = 'internetPairing'; + static const requested = 'requested'; + static const youDontHaveAnyMedicalReportsYet = 'youDontHaveAnyMedicalReportsYet'; + static const requestMedicalReport = 'requestMedicalReport'; + static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport'; + static const areYouSureYouWantToRequestMedicalReport = 'areYouSureYouWantToRequestMedicalReport'; + static const yourMedicalReportRequestSubmittedSuccessfully = 'yourMedicalReportRequestSubmittedSuccessfully'; + static const monthlyHealthSummaryReportDisclaimer = 'monthlyHealthSummaryReportDisclaimer'; + static const updatingMonthlyReportStatus = 'updatingMonthlyReportStatus'; + static const monthlyReportStatusUpdatedSuccessfully = 'monthlyReportStatusUpdatedSuccessfully'; + static const whoCanViewMyMedicalFile = 'whoCanViewMyMedicalFile'; + static const acceptedYourRequestToBeYourFamilyMember = 'acceptedYourRequestToBeYourFamilyMember'; + static const canViewYourFile = 'canViewYourFile'; + static const hasARequestPendingToBeYourFamilyMember = 'hasARequestPendingToBeYourFamilyMember'; + static const wantsToAddYouAsTheirFamilyMember = 'wantsToAddYouAsTheirFamilyMember'; + static const rejectedYourRequestToBeYourFamilyMember = 'rejectedYourRequestToBeYourFamilyMember'; + static const rejectedYourFamilyMemberRequest = 'rejectedYourFamilyMemberRequest'; + static const notAvailable = 'notAvailable'; + static const selectAProfile = 'selectAProfile'; + static const switchFamilyFile = 'switchFamilyFile'; + static const medicalFiles = 'medicalFiles'; + static const addANewFamilyMember = 'addANewFamilyMember'; + static const viewInvoiceDetails = 'viewInvoiceDetails'; + static const outPatient = 'outPatient'; + static const invoiceDetails = 'invoiceDetails'; + static const sendingEmailPleaseWait = 'sendingEmailPleaseWait'; + static const emailSentSuccessfullyMessage = 'emailSentSuccessfullyMessage'; + static const discount = 'discount'; + static const paid = 'paid'; + static const fetchingInvoiceDetails = 'fetchingInvoiceDetails'; + static const scanQRCode = 'scanQRCode'; + static const parkingSlotDetails = 'parkingSlotDetails'; + static const slotNumber = 'slotNumber'; + static const basement = 'basement'; + static const parkingDate = 'parkingDate'; + static const parkedSince = 'parkedSince'; + static const resetDirection = 'resetDirection'; + static const noPrescriptionOrdersYet = 'noPrescriptionOrdersYet'; + static const fetchingPrescriptionPDFPleaseWait = 'fetchingPrescriptionPDFPleaseWait'; + static const ratingValue = 'ratingValue'; + static const downloadPrescription = 'downloadPrescription'; + static const fetchingPrescriptionDetails = 'fetchingPrescriptionDetails'; + static const switchBackFamilyFile = 'switchBackFamilyFile'; + static const profileAndSettings = 'profileAndSettings'; + static const quickActions = 'quickActions'; + static const notificationsSettings = 'notificationsSettings'; + static const touchIDFaceIDServices = 'touchIDFaceIDServices'; + static const personalInformation = 'personalInformation'; + static const updateEmailAddress = 'updateEmailAddress'; + static const helpAndSupport = 'helpAndSupport'; + static const permissionsProfile = 'permissionsProfile'; + static const privacyPolicy = 'privacyPolicy'; + static const deactivateAccount = 'deactivateAccount'; + static const ageYearsOld = 'ageYearsOld'; + static const youDontHaveRadiologyOrders = 'youDontHaveRadiologyOrders'; + static const radiologyResult = 'radiologyResult'; + static const viewRadiologyImage = 'viewRadiologyImage'; + static const rateClinic = 'rateClinic'; + static const back = 'back'; + static const rateDoctor = 'rateDoctor'; + static const howWasYourLastVisitWithDoctor = 'howWasYourLastVisitWithDoctor'; + static const dateOfBirthSymptoms = 'dateOfBirthSymptoms'; + static const genderSymptoms = 'genderSymptoms'; + static const heightSymptoms = 'heightSymptoms'; + static const weightSymptoms = 'weightSymptoms'; + static const femaleGender = 'femaleGender'; + static const previous = 'previous'; + static const selectedOrgans = 'selectedOrgans'; + static const noOrgansSelected = 'noOrgansSelected'; + static const organSelector = 'organSelector'; + static const noPredictionsAvailable = 'noPredictionsAvailable'; + static const areYouSureYouWantToRestartOrganSelection = 'areYouSureYouWantToRestartOrganSelection'; + static const possibleConditions = 'possibleConditions'; + static const pleaseSelectAtLeastOneRiskBeforeProceeding = 'pleaseSelectAtLeastOneRiskBeforeProceeding'; + static const aboveYouSeeCommonRiskFactors = 'aboveYouSeeCommonRiskFactors'; + static const readMore = 'readMore'; + static const riskFactors = 'riskFactors'; + static const noRiskFactorsFound = 'noRiskFactorsFound'; + static const basedOnYourSelectedSymptomsNoRiskFactors = 'basedOnYourSelectedSymptomsNoRiskFactors'; } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f336c5b..fc400c4 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -275,11 +275,11 @@ class _LandingPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "How are you feeling today?".needTranslation.toText14(isBold: true), - "Check your symptoms with this scale".needTranslation.toText12(fontWeight: FontWeight.w500), + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(fontWeight: FontWeight.w500), SizedBox(height: 14.h), CustomButton( - text: "Check your symptoms".needTranslation, + text: LocaleKeys.checkYourSymptoms.tr(context: context), onPressed: () async { context.navigateWithName(AppRoutes.userInfoSelection); }, @@ -416,7 +416,7 @@ class _LandingPageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -476,7 +476,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), + LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, @@ -503,10 +503,10 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Quick Links".needTranslation.toText16(isBold: true), + LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), Row( children: [ - "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewMedicalFile.tr(context: context).toText12(color: AppColors.primaryRedColor), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -664,7 +664,7 @@ class _LandingPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - labelText: myAppointmentsViewModel.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, + labelText: myAppointmentsViewModel.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr() : LocaleKeys.yourTurn.tr(), backgroundColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.20), textColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), ), @@ -672,9 +672,9 @@ class _LandingPageState extends State { ], ), SizedBox(height: 8.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + LocaleKeys.halaFirstName.tr(namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}).toText16(isBold: true), SizedBox(height: 2.h), - "Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + LocaleKeys.thankYouForPatience.tr().toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 8.h), myAppointmentsViewModel.currentPatientQueueDetails.queueNo!.toText28(isBold: true), SizedBox(height: 6.h), @@ -683,7 +683,7 @@ class _LandingPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - "Serving Now: ".needTranslation.toText14(isBold: true), + "${LocaleKeys.servingNow.tr()}: ".toText14(isBold: true), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -691,7 +691,7 @@ class _LandingPageState extends State { SizedBox(width: 8.w), AppCustomChipWidget( deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, - labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, + labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? LocaleKeys.callForVitalSigns.tr() : LocaleKeys.callForDoctor.tr(), iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 14.w, @@ -746,7 +746,7 @@ class _LandingPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Immediate LiveCare Request".needTranslation.toText16(isBold: true), + LocaleKeys.immediateLiveCareRequest.tr(context: context).toText16(isBold: true), SizedBox(height: 10.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -769,14 +769,14 @@ class _LandingPageState extends State { ], ), SizedBox(height: 10.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + LocaleKeys.halaFirstName.tr(namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}, context: context).toText16(isBold: true), SizedBox(height: 8.h), - "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), + LocaleKeys.yourTurnIsAfterPatients.tr(namedArgs: {'count': immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount.toString()}, context: context).toText14(isBold: true), SizedBox(height: 8.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Expected waiting time: ".needTranslation.toText12(isBold: true), + "${LocaleKeys.waitingTime.tr()}: ".toText12(isBold: true), SizedBox(height: 7.h), ValueListenableBuilder( valueListenable: immediateLiveCareViewModel.durationNotifier, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index b2649f9..9058c8f 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -1,3 +1,4 @@ +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'; @@ -5,6 +6,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -115,7 +117,7 @@ class HabibWalletCard extends StatelessWidget { CustomButton( icon: AppAssets.recharge_icon, iconSize: 18.h, - text: "Recharge".needTranslation, + text: LocaleKeys.recharge.tr(context: context), onPressed: () {}, backgroundColor: AppColors.infoColor, borderColor: AppColors.infoColor, diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index 5274d5b..6025ba8 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -98,7 +98,7 @@ class LargeServiceCard extends StatelessWidget { ], ).paddingSymmetrical(16.w, 20.h), CustomButton( - text: serviceCardData.isBold ? "Visit Pharmacy Online".needTranslation : LocaleKeys.bookNow.tr(context: context), + text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr() : LocaleKeys.bookNow.tr(context: context), onPressed: () { handleOnTap(); }, diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart index 8ef0697..1bb17b5 100644 --- a/lib/presentation/home/widgets/welcome_widget.dart +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class WelcomeWidget extends StatelessWidget { @@ -31,7 +33,7 @@ class WelcomeWidget extends StatelessWidget { spacing: 4.h, mainAxisSize: MainAxisSize.min, children: [ - "Welcome".needTranslation.toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), + LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), Row( spacing: 4.h, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/home_health_care/hhc_order_detail_page.dart b/lib/presentation/home_health_care/hhc_order_detail_page.dart index 92c93d1..7017441 100644 --- a/lib/presentation/home_health_care/hhc_order_detail_page.dart +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -10,6 +10,7 @@ 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/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_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/chip/app_custom_chip_widget.dart'; @@ -121,7 +122,7 @@ class _HhcOrderDetailPageState extends State { Row( children: [ if (!isLoading) ...[ - "Request ID:".needTranslation.toText14( + LocaleKeys.requestID.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -186,7 +187,7 @@ class _HhcOrderDetailPageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any Home Health Care orders yet.".needTranslation, + noDataText: LocaleKeys.dontHaveHHCOrders.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -199,7 +200,7 @@ class _HhcOrderDetailPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "HHC Orders".needTranslation, + title: LocaleKeys.hhcOrders.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index be97a88..ef5dce5 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_order_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_selection_review_page.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart'; @@ -93,7 +94,7 @@ class _HhcProceduresPageState extends State { children: [ Row( children: [ - "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, weight: FontWeight.w500), SizedBox(width: 4.w), "${order.iD ?? '-'}".toText16(isBold: true), ], @@ -132,7 +133,7 @@ class _HhcProceduresPageState extends State { color: AppColors.primaryRedColor, ), SizedBox(width: 6.w), - "Requested Services".needTranslation.toText14( + LocaleKeys.requestedServices.tr().toText14( weight: FontWeight.w600, color: AppColors.blackColor, ), @@ -209,7 +210,7 @@ class _HhcProceduresPageState extends State { ), SizedBox(width: 8.w), Expanded( - child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + child: LocaleKeys.pendingOrderWait.tr(context: context).toText12( color: AppColors.infoBannerTextColor, fontWeight: FontWeight.w500, ), @@ -223,7 +224,7 @@ class _HhcProceduresPageState extends State { children: [ Expanded( child: CustomButton( - text: "Cancel Order".needTranslation, + text: LocaleKeys.cancelOrder.tr(context: context), onPressed: () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -248,7 +249,7 @@ class _HhcProceduresPageState extends State { hasBottomPadding: false, padding: EdgeInsets.only(top: 24.h), context, - title: 'Select Services'.needTranslation, + title: LocaleKeys.selectServices.tr(context: context), isCloseButtonVisible: true, isDismissible: true, callBackFunc: () {}, @@ -257,9 +258,9 @@ class _HhcProceduresPageState extends State { child: Padding( padding: EdgeInsets.all(24.h), child: Text( - 'No services available'.needTranslation, + LocaleKeys.noServicesAvailable.tr(context: context), style: TextStyle( - fontSize: 16.h, + fontSize: 16.f, color: AppColors.greyTextColor, ), ), @@ -300,7 +301,7 @@ class _HhcProceduresPageState extends State { duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, width: 24.w, - height: 24.w, + height: 24.h, decoration: BoxDecoration( color: isSelected ? AppColors.primaryRedColor : Colors.transparent, borderRadius: BorderRadius.circular(5.r), @@ -353,7 +354,7 @@ class _HhcProceduresPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Selected Services".needTranslation.toText12( + LocaleKeys.selectedServices.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -368,7 +369,7 @@ class _HhcProceduresPageState extends State { SizedBox(height: 16.h), CustomButton( borderWidth: 0, - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () { Navigator.pop(context); _proceedWithSelectedService(); @@ -398,9 +399,9 @@ class _HhcProceduresPageState extends State { bool result = await navigationServices.push( CustomPageRoute( page: MapUtilityScreen( - confirmButtonString: "Submit Request ".needTranslation, - titleString: "Select Location", - subTitleString: "Please select the location".needTranslation, + confirmButtonString: LocaleKeys.submitRequest.tr(context: context), + titleString: LocaleKeys.selectLocation.tr(context: context), + subTitleString: LocaleKeys.pleaseSelectTheLocation.tr(context: context), isGmsAvailable: appState.isGMSAvailable, ), direction: AxisDirection.down), @@ -447,7 +448,7 @@ class _HhcProceduresPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Home Health Care".needTranslation, + title: LocaleKeys.homeHealthCare.tr(context: context), history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), bottomChild: Consumer( builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { @@ -466,7 +467,7 @@ class _HhcProceduresPageState extends State { padding: EdgeInsets.all(24.w), child: CustomButton( borderWidth: 0, - text: "Create new request".needTranslation, + text: LocaleKeys.createNewRequest.tr(context: context), onPressed: () => _buildServicesListBottomsSheet(hmgServicesViewModel.hhcServicesList), textColor: AppColors.whiteColor, borderRadius: 12.r, @@ -494,7 +495,7 @@ class _HhcProceduresPageState extends State { Center( child: Utils.getNoDataWidget( context, - noDataText: "You have no pending requests.".needTranslation, + noDataText: LocaleKeys.youHaveNoPendingRequests.tr(context: context), ), ), ], diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart index 37410e2..ae2430a 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -50,7 +50,7 @@ class _HhcSelectionReviewPageState extends State { final isArabic = appState.isArabic(); return CollapsingListView( - title: "Summary".needTranslation, + title: LocaleKeys.summary.tr(context: context), bottomChild: _buildBottomButton(), child: SingleChildScrollView( padding: EdgeInsets.all(16.w), @@ -75,7 +75,7 @@ class _HhcSelectionReviewPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Selected Services".needTranslation.toText14( + LocaleKeys.selectedServices.tr(context: context).toText14( weight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -0.4, @@ -86,7 +86,7 @@ class _HhcSelectionReviewPageState extends State { runSpacing: 12.w, children: widget.selectedServices.map((service) { final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); - return AppCustomChipWidget(labelText: serviceName.needTranslation); + return AppCustomChipWidget(labelText: serviceName); }).toList(), ), ], @@ -110,7 +110,7 @@ class _HhcSelectionReviewPageState extends State { if (lat == 0.0 || lng == 0.0) return SizedBox.shrink(); // Get address from geocode response - String address = "Selected Location".needTranslation; + String address = LocaleKeys.selectLocation.tr(context: context); if (geocodeResponse != null && geocodeResponse.results.isNotEmpty) { address = geocodeResponse.results.first.formattedAddress; } @@ -133,7 +133,7 @@ class _HhcSelectionReviewPageState extends State { ), child: CustomButton( borderWidth: 0, - text: "Confirm".needTranslation, + text: LocaleKeys.confirm.tr(context: context), onPressed: () => _handleConfirm(), textColor: AppColors.whiteColor, borderRadius: 12.r, @@ -155,10 +155,10 @@ class _HhcSelectionReviewPageState extends State { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), Row( children: [ - "Here is your request #: ".needTranslation.toText14( + LocaleKeys.hereIsYourRequestNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -200,7 +200,7 @@ class _HhcSelectionReviewPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to submit this request?".needTranslation, + loadingText: LocaleKeys.confirmSubmitRequest.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); diff --git a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart index 688612c..d5310b3 100644 --- a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart +++ b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart @@ -25,7 +25,7 @@ class HhcUiSelectionHelper { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to cancel this order?".needTranslation, + loadingText: LocaleKeys.cancelOrderConfirmation.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -51,7 +51,7 @@ class HhcUiSelectionHelper { padding: EdgeInsets.all(16.w), child: Column( children: [ - Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.orderCancelledSuccessfully.tr(context: context)), SizedBox(height: 24.h), Row( children: [ diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index 415d66f..a6d8157 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -56,7 +56,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { AppCustomChipWidget( icon: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), iconColor: !insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, - labelText: insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : "Walk In".needTranslation, + labelText: insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), backgroundColor: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), textColor: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), ), @@ -137,7 +137,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Status:".needTranslation.toText14(isBold: true), + "${LocaleKeys.status.tr(context: context)}: ".toText14(isBold: true), insuranceApprovalResponseModel.apporvalDetails!.status!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), ], ), diff --git a/lib/presentation/insurance/insurance_approvals_page.dart b/lib/presentation/insurance/insurance_approvals_page.dart index b70c116..52f8b1f 100644 --- a/lib/presentation/insurance/insurance_approvals_page.dart +++ b/lib/presentation/insurance/insurance_approvals_page.dart @@ -95,7 +95,7 @@ class _InsuranceApprovalsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any insurance approvals yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noInsuranceApprovals.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index b005e42..04940fb 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -78,7 +78,7 @@ class _InsuranceHomePageState extends State { padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), child: Utils.getNoDataWidget( context, - noDataText: "You don't have insurance registered with HMG.".needTranslation, + noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), callToActionButton: CustomButton( icon: AppAssets.update_insurance_card_icon, iconColor: AppColors.successColor, diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index ee31538..588f988 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -54,7 +54,7 @@ class InsuranceApprovalCard extends StatelessWidget { ? "Walk In" : insuranceApprovalResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) - : "Walk In".needTranslation, + : LocaleKeys.walkin.tr(context: context), backgroundColor: isLoading ? AppColors.greyColor : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), textColor: isLoading ? AppColors.textColor : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), ).toShimmer2(isShow: isLoading), diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 341e234..7219a34 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -111,7 +111,7 @@ class InsuranceHistory extends StatelessWidget { ) : Utils.getNoDataWidget( context, - noDataText: "No insurance update requests found.".needTranslation, + noDataText: LocaleKeys.noInsuranceUpdateRequest.tr(context: context), // isSmallWidget: true, // width: 62, // height: 62, diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index c3bbcd7..c737732 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -90,7 +90,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { ], ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h) - : Utils.getNoDataWidget(context, noDataText: "No insurance data found...".needTranslation), + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noInsuranceDataFound.tr(context: context)), SizedBox( height: 24.h, ), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index fde5811..84fdee4 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -50,12 +50,12 @@ class PatientInsuranceCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.45, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true)), - "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".needTranslation.toText12(isBold: true, color: AppColors.lightGrayColor), + LocaleKeys.policyNumber.tr(namedArgs: {'number': insuranceCardDetailsModel.insurancePolicyNo ?? ''}, context: context).toText12(isBold: true, color: AppColors.lightGrayColor), ], ), AppCustomChipWidget( icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + labelText: isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12, @@ -78,7 +78,7 @@ class PatientInsuranceCard extends StatelessWidget { labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), - AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}".needTranslation), + AppCustomChipWidget(labelText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context)), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 837f482..2e4a96b 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -44,7 +44,7 @@ class LabOrderByTest extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - richText: '${"Last Tested:".needTranslation} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500), + richText: '${"${LocaleKeys.lastTested.tr(context: context)}:"} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500), backgroundColor: AppColors.greyLightColor, textColor: AppColors.textColor, ), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 90651f1..799f574 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/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 CollapsingToolbar( title: LocaleKeys.labResults.tr(), 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: AlwaysScrollableScrollPhysics(), 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, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // 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: AlwaysScrollableScrollPhysics(), 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} ${'results'.needTranslation}"), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...group.map((order) { 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: ("Order No: ".needTranslation + 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: "View Results".needTranslation, 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), ], ); }).toList(), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation)) : // 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 '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/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 CollapsingToolbar( title: LocaleKeys.labResults.tr(), 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: AlwaysScrollableScrollPhysics(), 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: AlwaysScrollableScrollPhysics(), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...group.map((order) { 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()}: ${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), ], ); }), ], ), ) : 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 ad4a032..50fd1f1 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -89,9 +89,9 @@ class LabResultByClinic extends StatelessWidget { hasShadow: true, ), child: CustomButton( - text: "Download report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Generating report, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr(context: context)); await labViewModel .getLabResultReportPDF( labOrder: labOrder, @@ -114,7 +114,7 @@ class LabResultByClinic extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart index 3f05443..6caa4a5 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart'; import 'package:provider/provider.dart' show Selector, Provider, ReadContext; @@ -17,8 +19,7 @@ class LabResultList extends StatelessWidget { builder: (__, list, ___) { if (list.isEmpty && context.read().labSpecialResult.isEmpty) { return Utils.getNoDataWidget(context, - noDataText: "You don't have any lab results yet." - .needTranslation); + noDataText: LocaleKeys.noLabResults.tr(context: context)); } else { return ListView.builder( physics: NeverScrollableScrollPhysics(), 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 c6841b3..e332066 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 @@ -71,7 +71,7 @@ class LabOrderResultItem extends StatelessWidget { child: Visibility( visible: tests?.referanceRange != null, child: Text( - "(Reference range: ${tests?.referanceRange})".needTranslation, + "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 1d54e06..eb39fef 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -32,7 +32,7 @@ class LabResultDetails extends StatelessWidget { @override Widget build(BuildContext context) { return CollapsingListView( - title: 'Lab Result Details'.needTranslation, + title: LocaleKeys.labResultDetails.tr(context: context), child: SingleChildScrollView( child: Column( spacing: 16.h, @@ -89,7 +89,7 @@ class LabResultDetails extends StatelessWidget { ], ), SizedBox(height: 4.h), - ("Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation).toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), + ("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), ], ), Row( @@ -116,7 +116,7 @@ class LabResultDetails extends StatelessWidget { Visibility( visible: recentLabResult.referanceRange != null, child: Text( - "Reference range: \n${recentLabResult.referanceRange!.trim()}".needTranslation, + "${LocaleKeys.referenceRange.tr(context: context)}: \n${recentLabResult.referanceRange!.trim()}", style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, @@ -261,13 +261,15 @@ class LabResultDetails extends StatelessWidget { leftLabelFormatter: (value) { value = double.parse(value.toStringAsFixed(1)); // return leftLabels(value.toStringAsFixed(2)); - if(value == labmodel.highRefrenceValue) - return leftLabels("High".needTranslation); + if (value == labmodel.highRefrenceValue) { + return leftLabels(LocaleKeys.high.tr()); + } - if(value== labmodel.lowRefenceValue) - return leftLabels("Low".needTranslation); + if (value == labmodel.lowRefenceValue) { + return leftLabels(LocaleKeys.low.tr()); + } - return SizedBox.shrink(); + return SizedBox.shrink(); // } }, graphColor:AppColors.blackColor, @@ -366,8 +368,7 @@ class LabResultDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - "What is this result?" - .needTranslation + LocaleKeys.whatIsThisResult.tr(context: context) .toText16(weight: FontWeight.w600, color: AppColors.textColor), testDescription?.toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? diff --git a/lib/presentation/medical_file/eye_measurement_details_page.dart b/lib/presentation/medical_file/eye_measurement_details_page.dart index 0662cb1..0822609 100644 --- a/lib/presentation/medical_file/eye_measurement_details_page.dart +++ b/lib/presentation/medical_file/eye_measurement_details_page.dart @@ -96,7 +96,7 @@ class EyeMeasurementDetailsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + LocaleKeys.leftEye.tr().toText14(isBold: true), SizedBox(height: 16.h), getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeSpherical}', '-'), getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeCylinder}', '-'), @@ -139,7 +139,7 @@ class EyeMeasurementDetailsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + LocaleKeys.leftEye.tr().toText14(isBold: true), SizedBox(height: 16.h), getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].brand}', ''), getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].baseCurve}', ''), diff --git a/lib/presentation/medical_file/eye_measurements_appointments_page.dart b/lib/presentation/medical_file/eye_measurements_appointments_page.dart index 3b82ad4..d8438ba 100644 --- a/lib/presentation/medical_file/eye_measurements_appointments_page.dart +++ b/lib/presentation/medical_file/eye_measurements_appointments_page.dart @@ -1,3 +1,4 @@ +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/utils/size_utils.dart'; @@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -75,7 +77,7 @@ class EyeMeasurementsAppointmentsPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No Ophthalmology appointments found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noOphthalmologyAppointments.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index c6ff85f..ad2461f 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -195,8 +195,8 @@ class _MedicalFilePageState extends State { ).withHorizontalPadding(24.w).onPress(() { DialogService dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutH( - label: "Family Files".needTranslation, - message: "This clinic or doctor is only available for the below eligible profiles.".needTranslation, + label: LocaleKeys.familyTitle.tr(context: context), + message: "", onSwitchPress: (FamilyFileResponseModelLists profile) { medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, @@ -289,7 +289,7 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, insuranceVM, child) { return AppCustomChipWidget( icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: insuranceVM.isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + labelText: insuranceVM.isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12.w, @@ -316,7 +316,7 @@ class _MedicalFilePageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.vitalSigns.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), Row( children: [ LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), @@ -359,7 +359,7 @@ class _MedicalFilePageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "No vital signs recorded yet".needTranslation.toText12(isCenter: true), + LocaleKeys.noVitalSignsRecordedYet.tr().toText12(isCenter: true), ], ), ), @@ -417,20 +417,20 @@ class _MedicalFilePageState extends State { }), SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record".needTranslation, - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.search_icon, - hintColor: AppColors.textColor, - ).paddingSymmetrical(24.w, 0.0), + // TextInputWidget( + // labelText: LocaleKeys.search.tr(context: context), + // hintText: "Type any record".needTranslation, + // controller: TextEditingController(), + // keyboardType: TextInputType.number, + // isEnable: true, + // prefix: null, + // autoFocus: false, + // isBorderAllowed: false, + // isAllowLeadingIcon: true, + // padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + // leadingIcon: AppAssets.search_icon, + // hintColor: AppColors.textColor, + // ).paddingSymmetrical(24.w, 0.0), SizedBox(height: 16.h), // Using CustomExpandableList CustomExpandableList( @@ -547,7 +547,7 @@ class _MedicalFilePageState extends State { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, + title: LocaleKeys.pickADate.tr(context: context), context, child: AppointmentCalendar(), isFullScreen: false, @@ -577,7 +577,7 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointments & visits".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.appointmentsAndVisits.tr().toText16(weight: FontWeight.w500, letterSpacing: -0.2), Row( children: [ LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), @@ -615,7 +615,7 @@ class _MedicalFilePageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any appointments. Please book an appointment".needTranslation.toText12(isCenter: true), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -662,7 +662,7 @@ class _MedicalFilePageState extends State { openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); }, onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); await myAppointmentsViewModel.isDoctorAvailable( projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, @@ -687,7 +687,6 @@ class _MedicalFilePageState extends State { }); } else { LoaderBottomSheet.hideLoader(); - print("Doctor is not available"); } }, onError: (_) { @@ -705,7 +704,7 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(0.w, 0.h); }), SizedBox(height: 10.h), - "Lab & Radiology".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.labAndRadiology.tr().toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), Row( children: [ @@ -728,7 +727,7 @@ class _MedicalFilePageState extends State { Expanded( child: LabRadCard( icon: AppAssets.radiology_icon, - labelText: "${LocaleKeys.radiology.tr(context: context)} Results".needTranslation, + labelText: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.results.tr(context: context)}", // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar", // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], labOrderTests: [], @@ -744,7 +743,7 @@ class _MedicalFilePageState extends State { ], ).paddingSymmetrical(0.w, 0.h), SizedBox(height: 24.h), - "Active Medications & Prescriptions".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.activeMedicationsAndPrescriptions.tr().toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading @@ -836,7 +835,7 @@ class _MedicalFilePageState extends State { children: [ Expanded( child: CustomButton( - text: "All Prescriptions".needTranslation, + text: LocaleKeys.allPrescriptions.tr(context: context), onPressed: () { Navigator.of(context).push( CustomPageRoute( @@ -859,7 +858,7 @@ class _MedicalFilePageState extends State { SizedBox(width: 6.w), Expanded( child: CustomButton( - text: "All Medications".needTranslation, + text: LocaleKeys.allMedications.tr(context: context), onPressed: () {}, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -887,7 +886,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any prescriptions yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -945,7 +944,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any completed visits yet".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyCompletedVisitsYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1017,7 +1016,7 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(0.w, 0); }), SizedBox(height: 24.h), - "Others".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -1031,7 +1030,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Eye Test Results".needTranslation, + label: LocaleKeys.eyeMeasurements.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, @@ -1048,7 +1047,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Allergy Info".needTranslation, + label: LocaleKeys.allergyInfo.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.allergy_info_icon, @@ -1063,7 +1062,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Vaccine Info".needTranslation, + label: LocaleKeys.vaccineInfo.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.vaccine_info_icon, @@ -1108,7 +1107,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have insurance registered with HMG.".needTranslation, + noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1153,7 +1152,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Update Insurance".needTranslation, + label: LocaleKeys.updateInsuranceInfo.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.update_insurance_icon, @@ -1177,7 +1176,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "My Invoices List".needTranslation, + label: LocaleKeys.myInvoicesList.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.invoices_list_icon, @@ -1191,7 +1190,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Ancillary Orders List".needTranslation, + label: LocaleKeys.ancillaryOrdersList.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.ancillary_orders_list_icon, @@ -1232,7 +1231,7 @@ class _MedicalFilePageState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any sick leaves yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -1267,7 +1266,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Medical Reports".needTranslation, + label: LocaleKeys.medicalReports.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.medical_reports_icon, @@ -1283,7 +1282,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Sick Leave Report".needTranslation, + label: LocaleKeys.sickLeaveReport.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.sick_leave_report_icon, @@ -1308,7 +1307,7 @@ class _MedicalFilePageState extends State { children: [ Row( children: [ - "Health Trackers".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.healthTrackers.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), ], ), SizedBox(height: 16.h), @@ -1324,7 +1323,7 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Blood Sugar".needTranslation, + label: LocaleKeys.bloodSugar.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.blood_sugar_icon, @@ -1332,7 +1331,7 @@ class _MedicalFilePageState extends State { iconSize: 36.w, ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)), MedicalFileCard( - label: "Blood Pressure".needTranslation, + label: LocaleKeys.bloodPressure.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.lab_result_icon, @@ -1340,7 +1339,7 @@ class _MedicalFilePageState extends State { iconSize: 36.w, ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)), MedicalFileCard( - label: "Weight Tracker".needTranslation, + label: LocaleKeys.weightTracker.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.weight_tracker_icon, @@ -1352,7 +1351,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Row( children: [ - "Others".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.others.tr().toText16(weight: FontWeight.w500, color: AppColors.textColor), ], ), SizedBox(height: 16.h), @@ -1368,21 +1367,21 @@ class _MedicalFilePageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Ask Your Doctor".needTranslation, + label: LocaleKeys.askYourDoctor.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.ask_doctor_medical_file_icon, isLargeText: true, iconSize: 36.w, ).onPress(() {}), - MedicalFileCard( - label: "Internet Pairing".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.internet_pairing_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() {}), + // MedicalFileCard( + // label: LocaleKeys.internetPairing.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.internet_pairing_icon, + // isLargeText: true, + // iconSize: 36.w, + // ).onPress(() {}), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index ef5aaeb..fcdb12e 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -245,7 +245,7 @@ class _PatientSickleavesListPageState extends State { Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async { @@ -293,7 +293,7 @@ class _PatientSickleavesListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any sick leaves yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 777426f..bf02cb7 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -1,5 +1,6 @@ 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_assets.dart'; @@ -9,6 +10,7 @@ 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/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -40,7 +42,7 @@ class _VaccineListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Vaccine Info".needTranslation, + title: LocaleKeys.vaccineInfo.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( @@ -170,7 +172,7 @@ class _VaccineListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No vaccines data found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDataAvailable.tr(context: context)); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index fbe79bb..3d3624d 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -183,7 +183,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { iconSize: 16.h, ) : CustomButton( - text: "Rebook".needTranslation, + text: LocaleKeys.rebook.tr(context: context), onPressed: () { onRescheduleTap(); }, diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index 6f9b8b5..16c05d8 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -94,7 +94,7 @@ class PatientSickLeaveCard extends StatelessWidget { : Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientSickLeavePDF(patientSickLeavesResponseModel, _appState.getAuthenticatedUser()!).then((val) async { @@ -106,7 +106,7 @@ class PatientSickLeaveCard extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/medical_report/medical_report_request_page.dart b/lib/presentation/medical_report/medical_report_request_page.dart index 8eabcdd..47a2df3 100644 --- a/lib/presentation/medical_report/medical_report_request_page.dart +++ b/lib/presentation/medical_report/medical_report_request_page.dart @@ -1,3 +1,4 @@ +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/utils/size_utils.dart'; @@ -6,6 +7,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -20,7 +22,7 @@ class MedicalReportRequestPage extends StatelessWidget { Widget build(BuildContext context) { medicalFileViewModel = Provider.of(context, listen: false); return CollapsingListView( - title: "Medical Reports".needTranslation, + title: LocaleKeys.medicalReports.tr(context: context), isClose: true, child: Column( children: [ diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index f6d7576..87420fd 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -44,7 +44,7 @@ class _MedicalReportsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Medical Reports".needTranslation, + title: LocaleKeys.medicalReports.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( @@ -88,7 +88,7 @@ class _MedicalReportsPageState extends State { Row( children: [ CustomButton( - text: "Requested".needTranslation, + text: LocaleKeys.requested.tr(context: context), onPressed: () { setState(() { expandedIndex = null; @@ -300,7 +300,7 @@ class _MedicalReportsPageState extends State { Expanded( flex: 6, child: CustomButton( - text: "Download Report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientMedicalReportPDF(report, appState.getAuthenticatedUser()!).then((val) async { @@ -348,7 +348,7 @@ class _MedicalReportsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnyMedicalReportsYet.tr(context: context)) .paddingSymmetrical(24.h, 24.h); }, ).paddingSymmetrical(24.h, 0.h), @@ -366,7 +366,7 @@ class _MedicalReportsPageState extends State { hasShadow: true, ), child: CustomButton( - text: "Request medical report".needTranslation, + text: LocaleKeys.requestMedicalReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { @@ -385,7 +385,7 @@ class _MedicalReportsPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "You do not have any appointments to request a medical report.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.youDoNotHaveAnyAppointmentsToRequestMedicalReport.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -414,7 +414,7 @@ class _MedicalReportsPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to request a medical report for this appointment?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToRequestMedicalReport.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -425,7 +425,7 @@ class _MedicalReportsPageState extends State { await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, - child: Utils.getSuccessWidget(loadingText: "Your medical report request has been successfully submitted.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.yourMedicalReportRequestSubmittedSuccessfully.tr(context: context)), callBackFunc: () { medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.onMedicalReportTabChange(0); diff --git a/lib/presentation/medical_report/widgets/patient_medical_report_card.dart b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart index 413858d..282a2d6 100644 --- a/lib/presentation/medical_report/widgets/patient_medical_report_card.dart +++ b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart @@ -144,7 +144,7 @@ class PatientMedicalReportCard extends StatelessWidget { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/monthly_report/monthly_report.dart b/lib/presentation/monthly_report/monthly_report.dart index 1776510..474863b 100644 --- a/lib/presentation/monthly_report/monthly_report.dart +++ b/lib/presentation/monthly_report/monthly_report.dart @@ -95,10 +95,7 @@ class MonthlyReport extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.w, height: 18.h), SizedBox(width: 9.h), Expanded( - child: - "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it’s not considered as a official report so no medical decision should be taken based on it" - .needTranslation - .toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight), + child: LocaleKeys.monthlyHealthSummaryReportDisclaimer.tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight), ), ], ), @@ -146,7 +143,7 @@ class MonthlyReport extends StatelessWidget { CustomButton( text: LocaleKeys.save.tr(), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Updating Monthly Report Status...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingMonthlyReportStatus.tr(context: context)); await monthlyReportVM.updatePatientHealthSummaryReport( rSummaryReport: monthlyReportVM.isHealthSummaryEnabled, onSuccess: (response) async { @@ -157,7 +154,7 @@ class MonthlyReport extends StatelessWidget { ); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Monthly Report Status Updated Successfully".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.monthlyReportStatusUpdatedSuccessfully.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart deleted file mode 100644 index d1a4d0c..0000000 --- a/lib/presentation/monthly_reports/monthly_reports_page.dart +++ /dev/null @@ -1,310 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/app_export.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/monthly_reports/monthly_reports_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/monthly_reports/user_agreement_page.dart'; -import 'package:provider/provider.dart'; - -import '../../generated/locale_keys.g.dart'; -import '../../theme/colors.dart'; -import '../../widgets/appbar/app_bar_widget.dart'; -import '../../widgets/input_widget.dart'; -import '../../widgets/loader/bottomsheet_loader.dart'; - -class MonthlyReportsPage extends StatefulWidget { - const MonthlyReportsPage({super.key}); - - @override - State createState() => _MonthlyReportsPageState(); -} - -class _MonthlyReportsPageState extends State { - bool isHealthSummaryEnabled = false; - bool isTermsAccepted = false; - - final TextEditingController emailController = TextEditingController(); - - @override - void dispose() { - emailController.dispose(); - super.dispose(); - } - - void _showError(String message) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - behavior: SnackBarBehavior.floating, - ), - ); - } - - void _showSuccessSnackBar() { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "Successfully updated".needTranslation, - style: const TextStyle( - color: AppColors.whiteColor, - fontWeight: FontWeight.w600, - ), - ), - behavior: SnackBarBehavior.floating, - backgroundColor: AppColors.textGreenColor, - duration: const Duration(seconds: 2), - ), - ); - } - - Future _onSavePressed() async { - if (!isTermsAccepted) { - _showError("Please accept the terms and conditions".needTranslation); - return; - } - - final email = emailController.text.trim(); - if (email.isEmpty) { - _showError("Please enter your email".needTranslation); - return; - } - - final vm = context.read(); - - // LoaderBottomSheet.showLoader(); - final ok = await vm.saveMonthlyReport(email: email); - // LoaderBottomSheet.hideLoader(); - - if (ok) { - setState(() => isHealthSummaryEnabled = true); - _showSuccessSnackBar(); - } else { - // _showError("Failed to update".needTranslation); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBgColor, - appBar: CustomAppBar( - onBackPressed: () => Navigator.of(context).pop(), - onLanguageChanged: (_) {}, - hideLogoAndLang: true, - ), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Monthly Reports".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 27.f, - fontWeight: FontWeight.w600, - ), - ), - SizedBox(height: 16.h), - - Container( - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - height: 54.h, - alignment: Alignment.center, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: (12.r), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Patient Health Summary Report".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - _buildToggle(), - ], - ), - ), - - SizedBox(height: 16.h), - - TextInputWidget( - controller: emailController, - labelText: "Eamil*".needTranslation, - hintText: "email@email.com", - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.emailAddress, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - onChange: (value) { - setState(() {}); - }, - ).paddingOnly(top: 8.h, bottom: 8.h), - - Row( - children: [ - Text( - "To View The Terms and Conditions".needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - InkWell( - child: Text( - "Click here".needTranslation, - style: TextStyle( - color: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - ), - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const UserAgreementPage(), - ), - ); - }, - ), - ], - ), - - SizedBox(height: 12.h), - - GestureDetector( - onTap: () => setState(() => isTermsAccepted = !isTermsAccepted), - child: Row( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: 24.h, - width: 24.h, - decoration: BoxDecoration( - color: isTermsAccepted - ? AppColors.textGreenColor - : Colors.transparent, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isTermsAccepted - ? AppColors.lightGreenColor - : AppColors.greyColor, - width: 2.h, - ), - ), - child: isTermsAccepted - ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor,) - : null, - ), - SizedBox(width: 12.h), - Text( - "I agree to the terms and conditions".needTranslation, - style: context.dynamicTextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w500, - color: AppColors.textColor, - ), - ), - ], - ), - ), - - SizedBox(height: 12.h), - - Text( - "This monthly Health Summary Report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as an official report so no medical decisions should be taken based on it" - .needTranslation, - style: TextStyle( - color: AppColors.textColor, - fontSize: 10.f, - fontWeight: FontWeight.w600, - ), - ), - - SizedBox(height: 12.h), - - Image.asset('assets/images/jpg/report.jpg'), - - SizedBox(height: 16.h), - - Row( - children: [ - Expanded( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.successColor, - foregroundColor: AppColors.whiteColor, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onPressed: _onSavePressed, - child: Text( - LocaleKeys.save.tr(), - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.f, - ), - ), - ), - ), - ], - ), - ], - ), - ).paddingAll(16), - ); - } - - Widget _buildToggle() { - final value = isHealthSummaryEnabled; - - return AbsorbPointer( - absorbing: true, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 50.h, - height: 28.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: value - ? AppColors.lightGreenColor - : AppColors.greyColor.withOpacity(0.3), - ), - child: AnimatedAlign( - duration: const Duration(milliseconds: 200), - alignment: value ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(3), - child: Container( - width: 22.h, - height: 22.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: value - ? AppColors.textGreenColor - : AppColors.greyTextColor, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/presentation/monthly_reports/user_agreement_page.dart b/lib/presentation/monthly_reports/user_agreement_page.dart deleted file mode 100644 index 73ea564..0000000 --- a/lib/presentation/monthly_reports/user_agreement_page.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/features/monthly_reports/terms_conditions_view_model.dart'; -import 'package:provider/provider.dart'; -import 'package:webview_flutter/webview_flutter.dart'; - -import '../../theme/colors.dart'; -import '../../widgets/appbar/app_bar_widget.dart'; - -class UserAgreementPage extends StatefulWidget { - const UserAgreementPage({super.key}); - - @override - State createState() => _UserAgreementPageState(); -} - -class _UserAgreementPageState extends State { - late final WebViewController _webViewController; - bool _isLoading = true; - String? _errorMessage; - - @override - void initState() { - super.initState(); - - _webViewController = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(const Color(0x00000000)) - ..setNavigationDelegate( - NavigationDelegate( - onPageStarted: (_) { - setState(() { - _isLoading = true; - }); - }, - onPageFinished: (_) { - setState(() { - _isLoading = false; - }); - }, - onWebResourceError: (error) { - }, - ), - ); - - WidgetsBinding.instance.addPostFrameCallback((_) { - final vm = - Provider.of(context, listen: false); - - vm.getTermsConditions( - onSuccess: () { - final htmlString = vm.termsConditionsHtml ?? ''; - - if (htmlString.isNotEmpty) { - setState(() { - _errorMessage = null; - _isLoading = true; - }); - _webViewController.loadHtmlString(htmlString); - } else { - setState(() { - _isLoading = false; - _errorMessage = 'لا توجد شروط متاحة حالياً'.needTranslation; - }); - } - }, - onError: (msg) { - setState(() { - _isLoading = false; - _errorMessage = msg; - }); - }, - ); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBgColor, - appBar: CustomAppBar( - onBackPressed: () => Navigator.of(context).pop(), - onLanguageChanged: (_) {}, - hideLogoAndLang: true, - ), - body: Stack( - children: [ - WebViewWidget(controller: _webViewController), - - if (_errorMessage != null) - Center( - child: Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - _errorMessage!, - textAlign: TextAlign.center, - style: TextStyle( - color: AppColors.primaryRedColor, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - if (_isLoading) - const Center( - child: CircularProgressIndicator(), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 07f1a4f..b16c587 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -50,7 +50,7 @@ class _FamilyMedicalScreenState extends State { AppState appState = getIt.get(); return CollapsingListView( - title: "Medical Files".needTranslation, + title: LocaleKeys.medicalFiles.tr(context: context), bottomChild: appState.getAuthenticatedUser()!.isParentUser! ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -59,13 +59,13 @@ class _FamilyMedicalScreenState extends State { ), padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), child: CustomButton( - text: "Add a new family member".needTranslation, + text: LocaleKeys.addANewFamilyMember.tr(context: context), onPressed: () { DialogService dialogService = getIt.get(); medicalVM!.clearAuthValues(); dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, + label: LocaleKeys.addFamilyMember.tr(context: context), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), onVerificationPress: () { medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 3621cc3..675da18 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -54,12 +54,10 @@ class _FamilyCardsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), SizedBox(width: 8.h), - "Who can view my medical file ?" - .needTranslation - .toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) + LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) .onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Manage Family".needTranslation, + label: LocaleKeys.manageFiles.tr(context: context), message: "", child: manageFamily(), onOkPressed: () {}, @@ -213,7 +211,7 @@ class _FamilyCardsState extends State { onPressed: () { if (canSwitch) widget.onSelect(profile); }, - text: isActive ? "Active".needTranslation : "Switch".needTranslation, + text: isActive ? LocaleKeys.active.tr(context: context) : LocaleKeys.switchLogin.tr(context: context), backgroundColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, borderColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, @@ -309,7 +307,7 @@ class _FamilyCardsState extends State { height: 30.h, chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Medical File: ${profile.patientId ?? "N/A".needTranslation}", + chipText: "${LocaleKeys.medicalFile.tr(context: context)}: ${profile.patientId ?? "N/A"}", iconAsset: null, isShowBorder: false, borderRadius: 8.h, @@ -364,26 +362,26 @@ class _FamilyCardsState extends State { switch (status) { case FamilyFileEnum.active: if (isRequestFromMySide) { - return "${status.displayName} your request to be your family member".needTranslation; + return LocaleKeys.acceptedYourRequestToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "can view your file".needTranslation; + return LocaleKeys.canViewYourFile.tr(context: context); } case FamilyFileEnum.pending: if (isRequestFromMySide) { - return "has a request ${status.displayName} to be your family member".needTranslation; + return LocaleKeys.hasARequestPendingToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "wants to add you as their family member".needTranslation; + return LocaleKeys.wantsToAddYouAsTheirFamilyMember.tr(context: context); } case FamilyFileEnum.rejected: if (isRequestFromMySide) { - return "${status.displayName} your request to be your family member".needTranslation; + return LocaleKeys.rejectedYourRequestToBeYourFamilyMember.tr(namedArgs: {'status': status.displayName}, context: context); } else { - return "${status.displayName} your family member request".needTranslation; + return LocaleKeys.rejectedYourFamilyMemberRequest.tr(namedArgs: {'status': status.displayName}, context: context); } case FamilyFileEnum.inactive: - return "Inactive".needTranslation; + return LocaleKeys.inactive.tr(context: context); default: - return "N/A".needTranslation; + return LocaleKeys.notAvailable.tr(context: context); } } } diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart index d469ab2..50f1b2a 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -15,8 +17,8 @@ class MyFamilySheet { titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Please select a profile'.needTranslation.toText21(isBold: true), - 'switch from the below list of medical file'.needTranslation.toText16(weight: FontWeight.w100, color: AppColors.greyTextColor), + LocaleKeys.selectAProfile.tr(context: context).toText21(isBold: true), + LocaleKeys.switchFamilyFile.tr(context: context).toText16(weight: FontWeight.w100, color: AppColors.greyTextColor), ], ), child: FamilyCards( diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart index cccd671..a38194f 100644 --- a/lib/presentation/my_invoices/my_invoices_details_page.dart +++ b/lib/presentation/my_invoices/my_invoices_details_page.dart @@ -39,9 +39,9 @@ class _MyInvoicesDetailsPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Invoice Details".needTranslation, + title: LocaleKeys.invoiceDetails.tr(context: context), sendEmail: () async { - LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); await myInvoicesViewModel.sendInvoiceEmail( appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!, projectID: widget.getInvoiceDetailsResponseModel.projectID!, @@ -49,7 +49,7 @@ class _MyInvoicesDetailsPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -223,12 +223,12 @@ class _MyInvoicesDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalBalance.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], @@ -236,7 +236,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), @@ -246,7 +246,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Discount".needTranslation.toText14(isBold: true), + LocaleKeys.discount.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor), AppColors.primaryRedColor, 13, isSaudiCurrency: true), @@ -255,7 +255,7 @@ class _MyInvoicesDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Paid".needTranslation.toText14(isBold: true), + LocaleKeys.paid.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13, isSaudiCurrency: true), diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index ef1a9c2..9f969e8 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -77,7 +77,7 @@ class _MyInvoicesListState extends State { getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], onTap: () async { myInvoicesVM.setInvoiceDetailLoading(); - LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); await myInvoicesVM.getInvoiceDetails( appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index 27ca79a..4a328c5 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -41,11 +42,11 @@ class InvoiceListCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.walkin_appointment_icon, iconColor: AppColors.textColor, - labelText: 'Walk In'.needTranslation, + labelText: LocaleKeys.walkin.tr(context: context), textColor: AppColors.textColor, ), AppCustomChipWidget( - labelText: 'OutPatient'.needTranslation, + labelText: LocaleKeys.outPatient.tr(context: context), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), textColor: AppColors.primaryRedColor, ), @@ -127,7 +128,7 @@ class InvoiceListCard extends StatelessWidget { ), SizedBox(height: 16.h), CustomButton( - text: "View invoice details".needTranslation, + text: LocaleKeys.viewInvoiceDetails.tr(context: context), onPressed: () { if (onTap != null) { onTap!(); diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 99d4270..c9a93ff 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -1,3 +1,4 @@ +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/utils/date_util.dart'; @@ -6,6 +7,7 @@ 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/notifications/notifications_view_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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -17,7 +19,7 @@ class NotificationsListPage extends StatelessWidget { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Notifications".needTranslation, + title: LocaleKeys.notifications.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, notificationsVM, child) { return Container( diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart index a40a27b..bfcdc96 100644 --- a/lib/presentation/onboarding/onboarding_screen.dart +++ b/lib/presentation/onboarding/onboarding_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -6,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -67,13 +69,13 @@ class _OnboardingScreenState extends State { children: [ onboardingView( AppAnimations.onboarding_1, - "Booking appointment has never been easy".needTranslation, - "In few clicks find yourself having consultation with the doctor of your choice.".needTranslation, + LocaleKeys.onboardingHeading1.tr(context: context), + LocaleKeys.onboardingBody1.tr(context: context), ), onboardingView( AppAnimations.onboarding_2, - "Access the medical history on finger tips".needTranslation, - "Keep track on your medical history including labs, prescription, insurance, etc".needTranslation, + LocaleKeys.onboardingHeading2.tr(context: context), + LocaleKeys.onboardingBody2.tr(context: context), ), ], onPageChanged: (int index) { @@ -107,7 +109,7 @@ class _OnboardingScreenState extends State { transitionBuilder: (child, anim) => FadeTransition(opacity: anim, child: child), child: selectedIndex == 0 ? CustomButton( - text: "Skip".needTranslation, + text: LocaleKeys.skip.tr(context: context), onPressed: () => goToHomePage(), width: 86.w, height: 56.h, @@ -136,13 +138,13 @@ class _OnboardingScreenState extends State { iconSize: 32.w, width: 86.w, height: 56.h, - text: "".needTranslation, + text: "", backgroundColor: Colors.transparent, onPressed: () { pageController.animateToPage(1, duration: Duration(milliseconds: 400), curve: Curves.easeInOut); }) : CustomButton( - text: "Get Started".needTranslation, + text: LocaleKeys.getStarted.tr(context: context), fontWeight: FontWeight.w500, fontSize: 16.f, height: 56.h, diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index d9cc1d8..db451b2 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -1,9 +1,10 @@ - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/parking/parking_slot.dart'; import 'package:provider/provider.dart'; @@ -85,7 +86,7 @@ class _ParkingPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Parking".needTranslation, + LocaleKeys.parking.tr(context: context), style: TextStyle( color: AppColors.textColor, fontSize: 27.f, @@ -100,22 +101,7 @@ class _ParkingPageState extends State { hasShadow: true, ), child: Padding( - padding: EdgeInsets.all(16.h), - child: Text( - "Dr. Sulaiman Al Habib hospital are conduction a test for the emerging corona" - " virus and issuing travel certificates 24/7 in a short time and with high accuracy." - " Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches " - "to conduct a corona test within few minutes. Dr. Sulaiman Al Habib hospital are conduction" - " a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. " - "Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes.", - style: TextStyle( - color: AppColors.textColor, - fontSize: 12, - height: 1.4, - fontWeight: FontWeight.w500, - ), - ), - ), + padding: EdgeInsets.all(16.h), child: LocaleKeys.parkingDescription.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColor)), ).paddingOnly(top: 16, bottom: 16), ], ), @@ -131,18 +117,16 @@ class _ParkingPageState extends State { ), child: Padding( padding: EdgeInsets.all(24.h), - child: SizedBox( - width: double.infinity, - height: 56, - child: CustomButton( - text: "Read Barcodes".needTranslation, - onPressed: () => _readQR(context), // always non-null - isDisabled: vm.isLoading, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - fontSize: 18, - fontWeight: FontWeight.bold, - ), + child: CustomButton( + text: LocaleKeys.scanQRCode.tr(context: context), + onPressed: () => _readQR(context), + // always non-null + isDisabled: vm.isLoading, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + fontSize: 18.f, + height: 56.h, + fontWeight: FontWeight.bold, ), ), ), diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 0eb3718..52ab181 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -1,10 +1,12 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/qr_parking/models/qr_parking_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; @@ -157,7 +159,7 @@ class _ParkingSlotState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Parking Slot Details".needTranslation, + LocaleKeys.parkingSlotDetails.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -170,16 +172,16 @@ class _ParkingSlotState extends State { runSpacing: 4, children: [ AppCustomChipWidget( - labelText: "Slot: ${widget.model.qRParkingCode ?? '-'}".needTranslation, + labelText: LocaleKeys.slotNumber.tr(namedArgs: {'code': widget.model.qRParkingCode ?? '-'}, context: context), ), AppCustomChipWidget( - labelText: "Basement: ${widget.model.floorDescription ?? '-'}".needTranslation, + labelText: LocaleKeys.basement.tr(namedArgs: {'description': widget.model.floorDescription ?? '-'}, context: context), ), AppCustomChipWidget( - labelText: "Date: ${_formatPrettyDate(widget.model.createdOn)}".needTranslation, + labelText: LocaleKeys.parkingDate.tr(namedArgs: {'date': _formatPrettyDate(widget.model.createdOn)}, context: context), ), AppCustomChipWidget( - labelText: "Parked Since: ${_formatPrettyTime(widget.model.createdOn)}".needTranslation, + labelText: LocaleKeys.parkedSince.tr(namedArgs: {'time': _formatPrettyTime(widget.model.createdOn)}, context: context), ), ], ), @@ -193,7 +195,7 @@ class _ParkingSlotState extends State { width: double.infinity, height: 48.h, child: CustomButton( - text: "Get Direction".needTranslation, + text: LocaleKeys.getDirections.tr(context: context), onPressed: _openDirection, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -210,14 +212,14 @@ class _ParkingSlotState extends State { width: double.infinity, height: 48.h, child: CustomButton( - text: "Reset Direction".needTranslation, + text: LocaleKeys.resetDirection.tr(context: context), onPressed: _resetDirection, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 18, + fontSize: 18.f, fontWeight: FontWeight.bold, - borderRadius: 10, + borderRadius: 10.r, ), ), ], diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart index 0bedbe2..5a34034 100644 --- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -118,7 +118,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.submit.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Submitting your request..."); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); await prescriptionsViewModel.submitPrescriptionDeliveryRequest( latitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat.toString(), longitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng.toString(), @@ -129,7 +129,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getSuccessWidget(loadingText: "Request sent successfully.".needTranslation), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), callBackFunc: () { Navigator.of(context).pop(); }, diff --git a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart index e2ce865..7760fd2 100644 --- a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart @@ -88,7 +88,7 @@ class PrescriptionDeliveryOrdersListPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any prescription orders yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noPrescriptionOrdersYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 1216c61..9b1ae6d 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -62,7 +62,7 @@ class _PrescriptionDetailPageState extends State { child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), instructions: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); if (prescriptionsViewModel.prescriptionInstructionsPDFLink.isNotEmpty) { @@ -71,7 +71,7 @@ class _PrescriptionDetailPageState extends State { } else { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Unable to fetch PDF".needTranslation), + child: Utils.getErrorWidget(loadingText: "Unable to fetch PDF"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -136,7 +136,7 @@ class _PrescriptionDetailPageState extends State { AppCustomChipWidget( icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, + labelText: LocaleKeys.ratingValue.tr(namedArgs: {'rating': widget.prescriptionsResponseModel.decimalDoctorRate.toString()}, context: context), ), AppCustomChipWidget( labelText: widget.prescriptionsResponseModel.name!, @@ -145,9 +145,9 @@ class _PrescriptionDetailPageState extends State { ), SizedBox(height: 16.h), CustomButton( - text: "Download Prescription".needTranslation, + text: LocaleKeys.downloadPrescription.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { LoaderBottomSheet.hideLoader(); if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { @@ -157,7 +157,7 @@ class _PrescriptionDetailPageState extends State { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -221,7 +221,7 @@ class _PrescriptionDetailPageState extends State { : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () async { if (widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!) { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); prescriptionsViewModel.initiatePrescriptionDelivery(); diff --git a/lib/presentation/prescriptions/prescription_reminder_view.dart b/lib/presentation/prescriptions/prescription_reminder_view.dart index 2f1154f..aab587d 100644 --- a/lib/presentation/prescriptions/prescription_reminder_view.dart +++ b/lib/presentation/prescriptions/prescription_reminder_view.dart @@ -54,7 +54,7 @@ class _PrescriptionReminderViewState extends State { ), child: RadioListTile( title: Text( - "${_options[index]} minutes before".needTranslation, + "${_options[index]} ${LocaleKeys.minute.tr(context: context)}", style: TextStyle( fontSize: 16.h, fontWeight: FontWeight.w500, diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 8b60159..e2a862b 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -247,7 +247,7 @@ class _PrescriptionsListPageState extends State { : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () async { if (prescription.isHomeMedicineDeliverySupported!) { - LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], onSuccess: (val) { LoaderBottomSheet.hideLoader(); @@ -322,7 +322,7 @@ class _PrescriptionsListPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 1c16439..0bf7058 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -83,7 +83,7 @@ class ProfileSettingsState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: "Profile & Settings".needTranslation, + title: LocaleKeys.profileAndSettings.tr(context: context), logout: () {}, isClose: true, child: SingleChildScrollView( @@ -114,8 +114,8 @@ class ProfileSettingsState extends State { onAddFamilyMemberPress: () { DialogService dialogService = getIt.get(); dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), onVerificationPress: () { medicalVm.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); @@ -149,7 +149,7 @@ class ProfileSettingsState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), - "Habib Wallet".needTranslation.toText16(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w600, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), @@ -165,7 +165,7 @@ class ProfileSettingsState extends State { iconSize: 22.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, - text: "Recharge".needTranslation, + text: LocaleKeys.recharge.tr(context: context), borderWidth: 0.w, fontWeight: FontWeight.w500, borderColor: Colors.transparent, @@ -183,9 +183,7 @@ class ProfileSettingsState extends State { ), ], ), - "Quick Actions" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) + LocaleKeys.quickActions.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) .paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), @@ -195,17 +193,15 @@ class ProfileSettingsState extends State { children: [ actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); - }, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation), + }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), 1.divider, - actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}), + actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () {}), 1.divider, - actionItem(AppAssets.touch_face_id, "Touch ID / Face ID Services".needTranslation, () {}, switchValue: true), + actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(), () {}, switchValue: true), ], ), ), - "Personal Information" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) + LocaleKeys.personalInformation.tr().toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) .paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), @@ -213,7 +209,7 @@ class ProfileSettingsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.email_transparent, "Update Email Address".needTranslation, () {}), + actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(), () {}), // 1.divider, // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), // 1.divider, @@ -223,9 +219,7 @@ class ProfileSettingsState extends State { ], ), ), - "Help & Support" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) + LocaleKeys.helpAndSupport.tr().toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) .paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), @@ -237,9 +231,9 @@ class ProfileSettingsState extends State { launchUrl(Uri.parse("tel://" + "+966 11 525 9999")); }, trailingLabel: "011 525 9999"), 1.divider, - actionItem(AppAssets.permission, "Permissions".needTranslation, () {}, trailingLabel: "Location, Camera"), + actionItem(AppAssets.permission, LocaleKeys.permissions.tr(), () {}, trailingLabel: "Location, Camera"), 1.divider, - actionItem(AppAssets.rate, "Rate Our App".needTranslation, () { + actionItem(AppAssets.rate, LocaleKeys.rateApp.tr(), () { if (Platform.isAndroid) { Utils.openWebView( url: 'https://play.google.com/store/apps/details?id=com.ejada.hmg', @@ -251,13 +245,13 @@ class ProfileSettingsState extends State { } }, isExternalLink: true), 1.divider, - actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () { + actionItem(AppAssets.privacy_terms, LocaleKeys.privacyPolicy.tr(), () { Utils.openWebView( url: 'https://hmg.com/en/Pages/Privacy.aspx', ); }, isExternalLink: true), 1.divider, - actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () { + actionItem(AppAssets.privacy_terms, LocaleKeys.termsConditoins.tr(context: context), () { Utils.openWebView( url: 'https://hmg.com/en/Pages/Terms.aspx', ); @@ -268,7 +262,7 @@ class ProfileSettingsState extends State { CustomButton( height: 56.h, icon: AppAssets.minus, - text: "Deactivate account".needTranslation, + text: LocaleKeys.deactivateAccount.tr(), onPressed: () {}, ).paddingAll(24.w), ], @@ -363,7 +357,7 @@ class FamilyCardWidget extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${profile.age} Years Old".needTranslation, + labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), ), isActive && appState.getAuthenticatedUser()!.bloodGroup != null ? AppCustomChipWidget( @@ -396,17 +390,17 @@ class FamilyCardWidget extends StatelessWidget { if (isLoading) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance".needTranslation; + labelText = LocaleKeys.insurance.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor; } else if (isExpired) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance Expired".needTranslation; + labelText = LocaleKeys.insuranceExpired.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); } else { icon = AppAssets.insurance_active_icon; - labelText = "Insurance Active".needTranslation; + labelText = LocaleKeys.insuranceActive.tr(context: context); iconColor = AppColors.successColor; backgroundColor = AppColors.successColor.withValues(alpha: 0.15); } @@ -451,7 +445,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, - text: canSwitch ? "Switch Family File".needTranslation : "Add a new family member".needTranslation, + text: canSwitch ? LocaleKeys.switchFamilyFile.tr() : LocaleKeys.addANewFamilyMember.tr(), onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, @@ -467,7 +461,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: AppAssets.switch_user, - text: canSwitchBack ? "Switch Back To Family File".needTranslation : "Switch".needTranslation, + text: canSwitchBack ? LocaleKeys.switchBackFamilyFile.tr() : LocaleKeys.switchLogin.tr(), backgroundColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, diff --git a/lib/presentation/profile_settings/widgets/family_card_widget.dart b/lib/presentation/profile_settings/widgets/family_card_widget.dart index eaee4c0..b21c52d 100644 --- a/lib/presentation/profile_settings/widgets/family_card_widget.dart +++ b/lib/presentation/profile_settings/widgets/family_card_widget.dart @@ -73,7 +73,7 @@ class FamilyCardWidget extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "${profile.age} Years Old".needTranslation, + labelText: "${profile.age} ${LocaleKeys.yearsOld.tr(context: context)}", ), isActive && appState.getAuthenticatedUser()!.bloodGroup != null ? AppCustomChipWidget( @@ -106,17 +106,17 @@ class FamilyCardWidget extends StatelessWidget { if (isLoading) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance".needTranslation; + labelText = LocaleKeys.insurance.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor; } else if (isExpired) { icon = AppAssets.cancel_circle_icon; - labelText = "Insurance Expired".needTranslation; + labelText = LocaleKeys.insuranceExpired.tr(context: context); iconColor = AppColors.primaryRedColor; backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); } else { icon = AppAssets.insurance_active_icon; - labelText = "Insurance Active".needTranslation; + labelText = LocaleKeys.insuranceActive.tr(context: context); iconColor = AppColors.successColor; backgroundColor = AppColors.successColor.withValues(alpha: 0.15); } @@ -161,7 +161,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, - text: canSwitch ? "Switch Family File".needTranslation : "Add a new family member".needTranslation, + text: canSwitch ? LocaleKeys.switchAccount.tr() : LocaleKeys.addANewFamilyMember.tr(), onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, @@ -177,7 +177,7 @@ class FamilyCardWidget extends StatelessWidget { return CustomButton( icon: AppAssets.switch_user, - text: canSwitchBack ? "Switch Back To Family File".needTranslation : "Switch".needTranslation, + text: canSwitchBack ? LocaleKeys.switchBackFamilyFile.tr() : LocaleKeys.switchLogin.tr(), backgroundColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index fb153ea..e1ec426 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -179,7 +179,7 @@ class _RadiologyOrdersPageState extends State { } if (model.patientRadiologyOrdersViewList.isEmpty) { - return Utils.getNoDataWidget(ctx, noDataText: "You don't have any radiology results yet.".needTranslation); + return Utils.getNoDataWidget(ctx, noDataText: LocaleKeys.youDontHaveRadiologyOrders.tr(context: context)); } return ListView.builder( @@ -239,7 +239,7 @@ class _RadiologyOrdersPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"), + AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), @@ -323,7 +323,7 @@ class _RadiologyOrdersPageState extends State { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: "View Results".needTranslation, + text: LocaleKeys.viewResults.tr(context: context), onPressed: () { model.navigationService.push( CustomPageRoute( diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 1fc2d9f..2328d0e 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -10,6 +11,7 @@ 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/radiology/models/resp_models/patient_radiology_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -49,7 +51,7 @@ class _RadiologyResultPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Radiology Result".needTranslation, + title: LocaleKeys.radiologyResult.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -72,13 +74,13 @@ class _RadiologyResultPageState extends State { widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), SizedBox(height: 16.h), CustomButton( - text: "View Radiology Image".needTranslation, + text: LocaleKeys.viewRadiologyImage.tr(context: context), onPressed: () async { if (radiologyViewModel.radiologyImageURL.isNotEmpty) { Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); } else { - Utils.showToast("Radiology image not available".needTranslation); + Utils.showToast("Radiology image not available"); } }, backgroundColor: AppColors.primaryRedColor, @@ -111,7 +113,7 @@ class _RadiologyResultPageState extends State { hasShadow: true, ), child: CustomButton( - text: "Download report".needTranslation, + text: LocaleKeys.downloadReport.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(); await radiologyViewModel.getRadiologyPDF(patientRadiologyResponseModel: widget.patientRadiologyResponseModel, authenticatedUser: _appState.getAuthenticatedUser()!, onError: (err) { @@ -132,7 +134,7 @@ class _RadiologyResultPageState extends State { } catch (ex) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + child: Utils.getErrorWidget(loadingText: "Cannot open file"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/rate_appointment/rate_appointment_clinic.dart b/lib/presentation/rate_appointment/rate_appointment_clinic.dart index 5fb1fa3..e7e29f6 100644 --- a/lib/presentation/rate_appointment/rate_appointment_clinic.dart +++ b/lib/presentation/rate_appointment/rate_appointment_clinic.dart @@ -1,3 +1,4 @@ +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/utils/size_utils.dart'; @@ -5,6 +6,7 @@ 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/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -77,9 +79,7 @@ class _RateAppointmentClinicState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - - "Rate Clinic".needTranslation.toText16(isBold: true), - + LocaleKeys.rateClinic.tr(context: context).toText16(isBold: true), SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -147,13 +147,13 @@ class _RateAppointmentClinicState extends State { children: [ Expanded( child: CustomButton( - text: "Back".needTranslation, + text: LocaleKeys.back.tr(context: context), backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), onPressed: () { - appointmentRatingViewModel!.setTitle("Rate Doctor".needTranslation); - appointmentRatingViewModel!.setSubTitle("How was your last visit with doctor?".needTranslation); + appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context)); + appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); appointmentRatingViewModel!.setClinicOrDoctor(false); setState(() { @@ -164,7 +164,7 @@ class _RateAppointmentClinicState extends State { SizedBox(width: 10), Expanded( child: CustomButton( - text: "Submit".needTranslation, + text: LocaleKeys.submit.tr(context: context), onPressed: () { submitRating(); diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index ac79744..b9a6c58 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_clinic.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/widget/doctor_row.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -88,9 +90,7 @@ class _RateAppointmentDoctorState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - - "Please rate the doctor".needTranslation.toText16(isBold: true), - + "Please rate the doctor".toText16(isBold: true), SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -142,7 +142,7 @@ class _RateAppointmentDoctorState extends State { maxLines: 4, decoration: InputDecoration.collapsed( - hintText: "Notes".needTranslation, + hintText: LocaleKeys.notes.tr(context: context), hintStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -172,7 +172,7 @@ class _RateAppointmentDoctorState extends State { children: [ Expanded( child: CustomButton( - text: "Later".needTranslation, + text: "Later", backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), @@ -184,11 +184,11 @@ class _RateAppointmentDoctorState extends State { SizedBox(width: 10), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () { // Set up clinic rating and show clinic rating view - appointmentRatingViewModel!.setTitle("Rate Clinic".needTranslation); - appointmentRatingViewModel!.setSubTitle("How was your appointment?".needTranslation); + appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),); + appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),); appointmentRatingViewModel!.setClinicOrDoctor(true); setState(() {}); diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 1786dec..cc956dc 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -9,6 +10,7 @@ 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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -38,11 +40,11 @@ class _OrganSelectorPageState extends State { void _onNextPressed(SymptomsCheckerViewModel viewModel) async { if (!viewModel.validateSelection()) { dialogService.showErrorBottomSheet( - message: 'Please select at least one organ'.needTranslation, + message: LocaleKeys.noOrgansSelected.tr(context: context), ); return; } - LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context),); final String userName = 'guest_user'; final String password = '123456'; @@ -112,7 +114,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - "Organ Selector".needTranslation, + LocaleKeys.organSelector.tr(context: context), style: TextStyle( color: AppColors.textColor, fontSize: 22.f, @@ -249,7 +251,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - 'Selected Organs'.needTranslation, + LocaleKeys.selectedOrgans.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -264,7 +266,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( - 'No organs selected yet'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( color: AppColors.greyTextColor, fontSize: 14.f, @@ -301,7 +303,7 @@ class _OrganSelectorPageState extends State { return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: CustomButton( - text: 'Next'.needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), isDisabled: viewModel.selectedOrgans.isEmpty, backgroundColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index a63d1e2..bc233a4 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -51,7 +51,7 @@ class PossibleConditionsPage extends StatelessWidget { child: Padding( padding: EdgeInsets.all(24.h), child: Text( - 'No Predictions available'.needTranslation, + LocaleKeys.noPredictionsAvailable.tr(context: context), style: TextStyle( fontSize: 16.h, color: AppColors.greyTextColor, @@ -102,7 +102,7 @@ class PossibleConditionsPage extends StatelessWidget { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToRestartOrganSelection.tr(context: context), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () => onConfirm(), @@ -161,7 +161,7 @@ class PossibleConditionsPage extends StatelessWidget { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Possible Conditions".needTranslation, + title: LocaleKeys.possibleConditions.tr(context: context), trailing: _buildTrailingSection(context), child: Consumer( builder: (context, symptomsCheckerViewModel, child) { diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index 8669c3c..6ee4f29 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +9,7 @@ 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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; @@ -44,7 +46,7 @@ class _RiskFactorsScreenState extends State { context.navigateWithName(AppRoutes.suggestionsPage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one risk before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneRiskBeforeProceeding.tr(context: context), ); } } @@ -121,11 +123,10 @@ class _RiskFactorsScreenState extends State { ), children: [ TextSpan( - text: "Above you see the most common risk factors. Although /diagnosis may return questions about risk factors, " - .needTranslation, + text: LocaleKeys.aboveYouSeeCommonRiskFactors.tr(context: context), ), TextSpan( - text: "read more".needTranslation, + text: LocaleKeys.readMore.tr(context: context), style: TextStyle( color: AppColors.primaryRedColor, fontWeight: FontWeight.w500, @@ -216,7 +217,7 @@ class _RiskFactorsScreenState extends State { children: [ Expanded( child: CollapsingListView( - title: "Risk Factors".needTranslation, + title: LocaleKeys.riskFactors.tr(context: context), leadingCallback: () => context.pop(), child: viewModel.isRiskFactorsLoading ? _buildLoadingShimmer() @@ -249,7 +250,7 @@ class _RiskFactorsScreenState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No risk factors found'.needTranslation, + LocaleKeys.noRiskFactorsFound.tr(context: context), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -258,7 +259,7 @@ class _RiskFactorsScreenState extends State { ), SizedBox(height: 8.h), Text( - 'Based on your selected symptoms, no additional risk factors were identified.'.needTranslation, + LocaleKeys.basedOnYourSelectedSymptomsNoRiskFactors.tr(context: context), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -282,7 +283,7 @@ class _RiskFactorsScreenState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -293,7 +294,7 @@ class _RiskFactorsScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart index 8366545..37802a2 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_export.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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -25,7 +27,7 @@ class AgeSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - "What is your Date of Birth?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor).paddingAll(24.w), + LocaleKeys.dateOfBirthSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor).paddingAll(24.w), SizedBox(height: 30.h), ThreeColumnDatePicker( enableHaptic: true, diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart index 85cb6e2..6eaae8c 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -5,6 +6,7 @@ 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/symptoms_checker/symptoms_checker_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:provider/provider.dart'; @@ -48,21 +50,21 @@ class GenderSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - "What is your gender?".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.genderSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor), SizedBox(height: 70.h), Row( children: [ Expanded( child: InkWell( onTap: () => onGenderSelected(genders[0]), - child: _buildGenderOption(AppAssets.maleIcon, "Male".needTranslation, symptomsViewModel.selectedGender == genders[0]), + child: _buildGenderOption(AppAssets.maleIcon, LocaleKeys.malE.tr(context: context), symptomsViewModel.selectedGender == genders[0]), ), ), SizedBox(width: 16.w), Expanded( child: InkWell( onTap: () => onGenderSelected(genders[1]), - child: _buildGenderOption(AppAssets.femaleIcon, "Female".needTranslation, symptomsViewModel.selectedGender == genders[1]), + child: _buildGenderOption(AppAssets.femaleIcon, LocaleKeys.femaleGender.tr(context: context), symptomsViewModel.selectedGender == genders[1]), )) ], ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart index 0744e81..65cf5a5 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -1,10 +1,12 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -107,7 +109,7 @@ class HeightSelectionPage extends StatelessWidget { SizedBox(height: 24.h), Center( child: Text( - 'How tall are you?'.needTranslation, + LocaleKeys.heightSymptoms.tr(context: context), style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart index 1d38a91..8c83796 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart @@ -1,10 +1,12 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -113,7 +115,7 @@ class WeightSelectionPage extends StatelessWidget { SizedBox(height: 24.h), Center( child: Text( - 'What is your weight?'.needTranslation, + LocaleKeys.weightSymptoms.tr(context: context), style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart index 523470e..b5df05b 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart @@ -1,10 +1,12 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/features/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/age_selection_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart'; @@ -184,7 +186,7 @@ class _UserInfoFlowManagerState extends State { child: isSingleEdit ? // Single page edit mode - show only Save button CustomButton( - text: "Save".needTranslation, + text: LocaleKeys.save.tr(context: context), onPressed: _onNext, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -197,7 +199,7 @@ class _UserInfoFlowManagerState extends State { if (!isFirstPage) ...[ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPrevious, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -209,7 +211,7 @@ class _UserInfoFlowManagerState extends State { ], Expanded( child: CustomButton( - text: isLastPage ? "Submit".needTranslation : "Next".needTranslation, + text: isLastPage ? LocaleKeys.submit.tr(context: context) : LocaleKeys.next.tr(context: context), onPressed: _onNext, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -233,7 +235,7 @@ class _UserInfoFlowManagerState extends State { Expanded( child: CollapsingListView( physics: NeverScrollableScrollPhysics(), - title: _pageTitles[_viewModel.userInfoCurrentPage].needTranslation, + title: _pageTitles[_viewModel.userInfoCurrentPage], isLeading: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index 87a8f3d..b89eac2 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -156,12 +158,12 @@ class ConditionCard extends StatelessWidget { ), _buildSymptomsRow(), SizedBox(height: 16.h), - Text("Description".needTranslation, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.f, color: AppColors.textColor)), + Text(LocaleKeys.description.tr(context: context), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.f, color: AppColors.textColor)), SizedBox(height: 2.h), Text(description, style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)), if (possibleConditionsSeverityEnum == PossibleConditionsSeverityEnum.emergency) CustomButton( - text: appointmentLabel ?? "Book Appointment".needTranslation, + text: appointmentLabel ?? LocaleKeys.bookAppointment.tr(context: context), onPressed: () { if (onActionPressed != null) { onActionPressed!(); diff --git a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart index c0f1be3..b66ef03 100644 --- a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart +++ b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_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/chip/app_custom_chip_widget.dart'; @@ -52,7 +54,7 @@ class _SelectedOrgansSectionState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Selected Organs'.needTranslation, + LocaleKeys.selectedOrgans.tr(context: context), style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w600, @@ -101,7 +103,7 @@ class _SelectedOrgansSectionState extends State { Padding( padding: EdgeInsets.symmetric(vertical: 8.h), child: Text( - 'No organs selected yet'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( color: AppColors.greyTextColor, fontSize: 14.f, From e5f24b9c06addc9b10dfe6a8819df1dc99b103bf Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 15 Jan 2026 14:05:32 +0300 Subject: [PATCH 44/46] updates --- lib/core/api_consts.dart | 2 +- lib/core/dependencies.dart | 15 +++++++-------- lib/main.dart | 4 ++++ lib/routes/app_routes.dart | 2 -- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 720fda7..efefee1 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -680,7 +680,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 45cd2a4..44a8bde 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -61,7 +61,6 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.da import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; -import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -299,13 +298,13 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - // getIt.registerFactory( - // () => QrParkingViewModel( - // qrParkingRepo: getIt(), - // errorHandlerService: getIt(), - // cacheService: getIt(), - // ), - // ); + getIt.registerFactory( + () => QrParkingViewModel( + qrParkingRepo: getIt(), + errorHandlerService: getIt(), + cacheService: getIt(), + ), + ); } } diff --git a/lib/main.dart b/lib/main.dart index 5158706..ec3b7ec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,6 +32,7 @@ import 'package:hmg_patient_app_new/features/notifications/notifications_view_mo import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; @@ -191,6 +192,9 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), ) diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index ebc7165..bf665ea 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -37,7 +37,6 @@ import '../presentation/covid19test/covid19_landing_page.dart'; import '../core/dependencies.dart'; import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; -import '../presentation/monthly_reports/monthly_reports_page.dart'; import '../presentation/parking/paking_page.dart'; import '../services/error_handler_service.dart'; import 'package:provider/provider.dart'; @@ -140,7 +139,6 @@ class AppRoutes { monthlyReportsRepo: getIt(), errorHandlerService: getIt(), ), - child: const MonthlyReportsPage(), ), qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), From 39af08ddc3cea7750d2f3f525b3a585d033ecccf Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Fri, 16 Jan 2026 13:03:04 +0300 Subject: [PATCH 45/46] Translation updates --- assets/langs/ar-SA.json | 71 ++++++++++++++++++- assets/langs/en-US.json | 69 +++++++++++++++++- lib/generated/locale_keys.g.dart | 67 +++++++++++++++++ .../allergies/allergies_list_page.dart | 2 +- .../notification_details_page.dart | 13 ++-- .../smartwatch_instructions_page.dart | 12 ++-- .../smartwatches/widgets/health_metric.dart | 6 +- .../symptoms_checker/suggestions_screen.dart | 14 ++-- .../symptoms_selector_screen.dart | 14 ++-- .../symptoms_checker/triage_screen.dart | 23 +++--- .../symptoms_checker/user_info_selection.dart | 26 +++---- .../ancillary_order_payment_page.dart | 34 ++++----- .../ancillary_procedures_details_page.dart | 26 +++---- lib/presentation/todo_section/todo_page.dart | 4 +- .../widgets/ancillary_orders_list.dart | 16 ++--- .../widgets/ancillary_procedures_list.dart | 19 ++--- lib/widgets/common_bottom_sheet.dart | 6 +- lib/widgets/countdown_timer.dart | 8 ++- 18 files changed, 322 insertions(+), 108 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f49873a..e304710 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1368,5 +1368,72 @@ "readMore": "اقرأ المزيد", "riskFactors": "عوامل الخطر", "noRiskFactorsFound": "لم يتم العثور على عوامل خطر", - "basedOnYourSelectedSymptomsNoRiskFactors": "بناءً على الأعراض المحددة، لم يتم تحديد عوامل خطر إضافية." -} \ No newline at end of file + "basedOnYourSelectedSymptomsNoRiskFactors": "بناءً على الأعراض المحددة، لم يتم تحديد عوامل خطر إضافية.", + "messageNotification": "الرسالة", + "attachedImage": "الصورة المرفقة", + "failedToLoadImage": "فشل تحميل الصورة", + "typeNotification": "النوع", + "pleaseSelectAtLeastOneOptionBeforeProceeding": "يرجى اختيار خيار واحد على الأقل قبل المتابعة", + "suggestions": "الاقتراحات", + "pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً", + "symptomsSelector": "محدد الأعراض", + "emergencyTriage": "طوارئ", + "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", + "noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة", + "pleaseAnswerAllQuestionsBeforeProceeding": "يرجى الإجابة على جميع الأسئلة قبل المتابعة", + "triage": "الفرز", + "areYouSureYouWantToExitProgress": "هل أنت متأكد أنك تريد الخروج؟ سيتم فقدان تقدمك.", + "noQuestionAvailable": "لا يوجد سؤال متاح", + "possibleSymptom": "عرض محتمل: ", + "symptomsCheckerFindingScore": "- درجة نتائج فاحص الأعراض", + "notSet": "غير محدد", + "years": "سنوات", + "symptomsChecker": "فاحص الأعراض", + "helloIsYourInformationUpToDate": "مرحباً {name}، هل معلوماتك محدثة؟", + "noEditAll": "لا، تعديل الكل", + "yesItIs": "نعم، إنها كذلك", + "age": "العمر", + "youDontHaveAnyAncillaryOrdersYet": "ليس لديك أي طلبات مساعدة بعد.", + "invoiceWithNumber": "الفاتورة: {invoiceNo}", + "queued": "في قائمة الانتظار", + "checkInReady": "جاهز للتسجيل", + "checkIn": "تسجيل الوصول", + "viewDetails": "عرض التفاصيل", + "selectPaymentMethod": "اختر طريقة الدفع", + "processingPaymentPleaseWait": "جاري معالجة الدفع، يرجى الانتظار...", + "finalizingPaymentPleaseWait": "جاري إتمام الدفع، يرجى الانتظار...", + "generatingInvoicePleaseWait": "جاري إنشاء الفاتورة، يرجى الانتظار...", + "hereIsYourInvoiceNumber": "هذا هو رقم فاتورتك #: ", + "paymentCompletedSuccessfully": "تم الدفع بنجاح", + "failedToInitializeApplePay": "فشل في تهيئة Apple Pay. يرجى المحاولة مرة أخرى.", + "cash": "نقدي", + "approved": "موافق عليه", + "approvalRejectedPleaseVisitReceptionist": "تم رفض الموافقة - يرجى زيارة موظف الاستقبال", + "sentForApproval": "تم إرساله للموافقة", + "ancillaryOrderDetails": "تفاصيل الطلب المساعد", + "noProceduresAvailableForSelectedOrder": "لا توجد إجراءات متاحة للطلب المحدد.", + "procedures": "الإجراءات", + "totalAmount": "المبلغ الإجمالي", + "covered": "مغطى", + "vatPercent": "ضريبة القيمة المضافة (15%)", + "proceedToPayment": "المتابعة للدفع", + "supportedSmartWatches": "الساعات الذكية المدعومة", + "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", + "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", + "viewWatchInstructions": "عرض تعليمات الساعة", + "healthConnectAppNotInstalled": "يبدو أنه ليس لديك تطبيق Health Connect مثبتًا. يرجى تثبيته من متجر Play لمزامنة بيانات صحتك.", + "setTimerOfReminder": "ضبط مؤقت التذكير", + "youHaveAppointmentWithDr": "لديك موعد مع د. ", + "hours": "ساعات", + "secs": "ثواني", + "noAllergiesDataFound": "لم يتم العثور على بيانات الحساسية...", + "heartRateDescription": "معدل ضربات قلبك يشير إلى عدد المرات التي ينبض فيها قلبك في الدقيقة", + "bloodOxygenDescription": "مستوى الأكسجين في الدم يشير إلى كمية الأكسجين التي تحملها خلايا الدم الحمراء", + "stepsDescription": "عدد الخطوات المتخذة على مدار اليوم", + "caloriesDescription": "السعرات الحرارية المحروقة أثناء النشاط البدني", + "distanceDescription": "المسافة المقطوعة على مدار اليوم", + "overview": "نظرة عامة", + "details": "التفاصيل", + "healthy": "صحي", + "warning": "تحذير" +} diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index c4b6c19..98ca7ad 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1361,5 +1361,72 @@ "readMore": "Read more", "riskFactors": "Risk Factors", "noRiskFactorsFound": "No risk factors found", - "basedOnYourSelectedSymptomsNoRiskFactors": "Based on your selected symptoms, no additional risk factors were identified." + "basedOnYourSelectedSymptomsNoRiskFactors": "Based on your selected symptoms, no additional risk factors were identified.", + "messageNotification": "Message", + "attachedImage": "Attached Image", + "failedToLoadImage": "Failed to load image", + "typeNotification": "Type", + "pleaseSelectAtLeastOneOptionBeforeProceeding": "Please select at least one option before proceeding", + "suggestions": "Suggestions", + "pleaseGoBackAndSelectOrgansFirst": "Please go back and select organs first", + "symptomsSelector": "Symptoms Selector", + "emergencyTriage": "Emergency", + "emergencyEvidenceDetected": "Emergency evidence detected. Please seek medical attention.", + "noQuestionItemsAvailable": "No question items available", + "pleaseAnswerAllQuestionsBeforeProceeding": "Please answer all questions before proceeding", + "triage": "Triage", + "areYouSureYouWantToExitProgress": "Are you sure you want to exit? Your progress will be lost.", + "noQuestionAvailable": "No question available", + "possibleSymptom": "Possible symptom: ", + "symptomsCheckerFindingScore": "- Symptoms checker finding score", + "notSet": "Not set", + "years": "Years", + "symptomsChecker": "Symptoms Checker", + "helloIsYourInformationUpToDate": "Hello {name}, Is your information up to date?", + "noEditAll": "No, Edit all", + "yesItIs": "Yes, It is", + "age": "Age", + "youDontHaveAnyAncillaryOrdersYet": "You don't have any ancillary orders yet.", + "invoiceWithNumber": "Invoice: {invoiceNo}", + "queued": "Queued", + "checkInReady": "Check-in Ready", + "checkIn": "Check In", + "viewDetails": "View Details", + "selectPaymentMethod": "Select Payment Method", + "processingPaymentPleaseWait": "Processing payment, Please wait...", + "finalizingPaymentPleaseWait": "Finalizing payment, Please wait...", + "generatingInvoicePleaseWait": "Generating invoice, Please wait...", + "hereIsYourInvoiceNumber": "Here is your invoice #: ", + "paymentCompletedSuccessfully": "Payment Completed Successfully", + "failedToInitializeApplePay": "Failed to initialize Apple Pay. Please try again.", + "cash": "Cash", + "approved": "Approved", + "approvalRejectedPleaseVisitReceptionist": "Approval Rejected - Please visit receptionist", + "sentForApproval": "Sent For Approval", + "ancillaryOrderDetails": "Ancillary Order Details", + "noProceduresAvailableForSelectedOrder": "No Procedures available for the selected order.", + "procedures": "Procedures", + "totalAmount": "Total Amount", + "covered": "Covered", + "vatPercent": "VAT (15%)", + "proceedToPayment": "Proceed to Payment", + "supportedSmartWatches": "Supported Smart Watches", + "pleaseMakeSureSamsungWatchConnected": "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.", + "beforeSyncingDataFollowInstructions": "Before syncing data, please make sure that you have followed the instructions properly.", + "viewWatchInstructions": "View watch instructions", + "healthConnectAppNotInstalled": "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.", + "setTimerOfReminder": "Set the timer of reminder", + "youHaveAppointmentWithDr": "You have appointment with Dr. ", + "hours": "Hours", + "secs": "Secs", + "noAllergiesDataFound": "No allergies data found...", + "heartRateDescription": "Your heart rate indicates how many times your heart beats per minute", + "bloodOxygenDescription": "Blood oxygen level indicates how much oxygen your red blood cells are carrying", + "stepsDescription": "Number of steps taken throughout the day", + "caloriesDescription": "Calories burned during physical activity", + "distanceDescription": "Distance covered throughout the day", + "overview": "Overview", + "details": "Details", + "healthy": "Healthy", + "warning": "Warning" } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index a3989ad..dc0655d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1363,5 +1363,72 @@ abstract class LocaleKeys { static const riskFactors = 'riskFactors'; static const noRiskFactorsFound = 'noRiskFactorsFound'; static const basedOnYourSelectedSymptomsNoRiskFactors = 'basedOnYourSelectedSymptomsNoRiskFactors'; + static const messageNotification = 'messageNotification'; + static const attachedImage = 'attachedImage'; + static const failedToLoadImage = 'failedToLoadImage'; + static const typeNotification = 'typeNotification'; + static const pleaseSelectAtLeastOneOptionBeforeProceeding = 'pleaseSelectAtLeastOneOptionBeforeProceeding'; + static const suggestions = 'suggestions'; + static const pleaseGoBackAndSelectOrgansFirst = 'pleaseGoBackAndSelectOrgansFirst'; + static const symptomsSelector = 'symptomsSelector'; + static const emergencyTriage = 'emergencyTriage'; + static const emergencyEvidenceDetected = 'emergencyEvidenceDetected'; + static const noQuestionItemsAvailable = 'noQuestionItemsAvailable'; + static const pleaseAnswerAllQuestionsBeforeProceeding = 'pleaseAnswerAllQuestionsBeforeProceeding'; + static const triage = 'triage'; + static const areYouSureYouWantToExitProgress = 'areYouSureYouWantToExitProgress'; + static const noQuestionAvailable = 'noQuestionAvailable'; + static const possibleSymptom = 'possibleSymptom'; + static const symptomsCheckerFindingScore = 'symptomsCheckerFindingScore'; + static const notSet = 'notSet'; + static const years = 'years'; + static const symptomsChecker = 'symptomsChecker'; + static const helloIsYourInformationUpToDate = 'helloIsYourInformationUpToDate'; + static const noEditAll = 'noEditAll'; + static const yesItIs = 'yesItIs'; + static const age = 'age'; + static const youDontHaveAnyAncillaryOrdersYet = 'youDontHaveAnyAncillaryOrdersYet'; + static const invoiceWithNumber = 'invoiceWithNumber'; + static const queued = 'queued'; + static const checkInReady = 'checkInReady'; + static const checkIn = 'checkIn'; + static const viewDetails = 'viewDetails'; + static const selectPaymentMethod = 'selectPaymentMethod'; + static const processingPaymentPleaseWait = 'processingPaymentPleaseWait'; + static const finalizingPaymentPleaseWait = 'finalizingPaymentPleaseWait'; + static const generatingInvoicePleaseWait = 'generatingInvoicePleaseWait'; + static const hereIsYourInvoiceNumber = 'hereIsYourInvoiceNumber'; + static const paymentCompletedSuccessfully = 'paymentCompletedSuccessfully'; + static const failedToInitializeApplePay = 'failedToInitializeApplePay'; + static const cash = 'cash'; + static const approved = 'approved'; + static const approvalRejectedPleaseVisitReceptionist = 'approvalRejectedPleaseVisitReceptionist'; + static const sentForApproval = 'sentForApproval'; + static const ancillaryOrderDetails = 'ancillaryOrderDetails'; + static const noProceduresAvailableForSelectedOrder = 'noProceduresAvailableForSelectedOrder'; + static const procedures = 'procedures'; + static const totalAmount = 'totalAmount'; + static const covered = 'covered'; + static const vatPercent = 'vatPercent'; + static const proceedToPayment = 'proceedToPayment'; + static const supportedSmartWatches = 'supportedSmartWatches'; + static const pleaseMakeSureSamsungWatchConnected = 'pleaseMakeSureSamsungWatchConnected'; + static const beforeSyncingDataFollowInstructions = 'beforeSyncingDataFollowInstructions'; + static const viewWatchInstructions = 'viewWatchInstructions'; + static const healthConnectAppNotInstalled = 'healthConnectAppNotInstalled'; + static const setTimerOfReminder = 'setTimerOfReminder'; + static const youHaveAppointmentWithDr = 'youHaveAppointmentWithDr'; + static const hours = 'hours'; + static const secs = 'secs'; + static const noAllergiesDataFound = 'noAllergiesDataFound'; + static const heartRateDescription = 'heartRateDescription'; + static const bloodOxygenDescription = 'bloodOxygenDescription'; + static const stepsDescription = 'stepsDescription'; + static const caloriesDescription = 'caloriesDescription'; + static const distanceDescription = 'distanceDescription'; + static const overview = 'overview'; + static const details = 'details'; + static const healthy = 'healthy'; + static const warning = 'warning'; } diff --git a/lib/presentation/allergies/allergies_list_page.dart b/lib/presentation/allergies/allergies_list_page.dart index efcdd0a..adb63f0 100644 --- a/lib/presentation/allergies/allergies_list_page.dart +++ b/lib/presentation/allergies/allergies_list_page.dart @@ -123,7 +123,7 @@ class AllergiesListPage extends StatelessWidget { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "No allergies data found...".needTranslation); + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noAllergiesDataFound.tr()); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/notifications/notification_details_page.dart b/lib/presentation/notifications/notification_details_page.dart index a4aef02..949b872 100644 --- a/lib/presentation/notifications/notification_details_page.dart +++ b/lib/presentation/notifications/notification_details_page.dart @@ -1,12 +1,13 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_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:intl/intl.dart'; import 'package:share_plus/share_plus.dart'; class NotificationDetailsPage extends StatelessWidget { @@ -28,7 +29,7 @@ class NotificationDetailsPage extends StatelessWidget { print('========================'); return CollapsingListView( - title: "Notification Details".needTranslation, + title: LocaleKeys.notificationDetails.tr(), trailing: IconButton( icon: Icon( Icons.share_outlined, @@ -115,7 +116,7 @@ class NotificationDetailsPage extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Message'.needTranslation.toText14( + LocaleKeys.messageNotification.tr().toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, ), @@ -153,7 +154,7 @@ class NotificationDetailsPage extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Attached Image'.needTranslation.toText14( + LocaleKeys.attachedImage.tr().toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, ), @@ -183,7 +184,7 @@ class NotificationDetailsPage extends StatelessWidget { color: AppColors.greyTextColor, ), SizedBox(height: 8.h), - 'Failed to load image'.needTranslation.toText12( + LocaleKeys.failedToLoadImage.tr().toText12( color: AppColors.greyTextColor, ), SizedBox(height: 4.h), @@ -233,7 +234,7 @@ class NotificationDetailsPage extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'Type'.needTranslation.toText14( + LocaleKeys.typeNotification.tr().toText14( weight: FontWeight.w600, color: AppColors.greyTextColor, ), diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 7f17f5a..8edbd24 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -38,7 +38,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Supported Smart Watches".needTranslation.toText20(isBold: true), + LocaleKeys.supportedSmartWatches.tr().toText20(isBold: true), SizedBox(height: 16.h), Row( children: [ @@ -161,15 +161,15 @@ class SmartwatchInstructionsPage extends StatelessWidget { ), ), SizedBox(height: 12), - "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.".needTranslation.toText14(isBold: true), - SizedBox(height: 12), - "Before syncing data, please make sure that you have followed the instructions properly.".needTranslation.toText14(isBold: true), + LocaleKeys.pleaseMakeSureSamsungWatchConnected.tr().toText14(isBold: true), + SizedBox(height: 8.h), + LocaleKeys.beforeSyncingDataFollowInstructions.tr().toText14(isBold: true), SizedBox(height: 12), InkWell( onTap: () { showInstructionsDialog(context); }, - child: "View watch instructions".needTranslation.toText12(isBold: true, color: AppColors.textColor, isUnderLine: true)), + child: LocaleKeys.viewWatchInstructions.tr().toText12(isBold: true, color: AppColors.textColor, isUnderLine: true)), SizedBox( height: 130.h, ), @@ -186,7 +186,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { ); } else { getIt.get().showErrorBottomSheet( - message: "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.".needTranslation, + message: LocaleKeys.healthConnectAppNotInstalled.tr(), onOkPressed: () { Navigator.pop(context); Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); diff --git a/lib/presentation/smartwatches/widgets/health_metric.dart b/lib/presentation/smartwatches/widgets/health_metric.dart index 5375966..43a5746 100644 --- a/lib/presentation/smartwatches/widgets/health_metric.dart +++ b/lib/presentation/smartwatches/widgets/health_metric.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class HealthMetricInfo { @@ -39,7 +41,7 @@ class HealthMetrics { unit: 'BPM', color: AppColors.primaryRedColor, icon: Icons.favorite, - description: "Your heart rate indicates how many times your heart beats per minute".needTranslation, + description: LocaleKeys.heartRateDescription.tr(), minHealthyValue: 60, maxHealthyValue: 100, svgIcon: "assets/images/smartwatches/heartrate_icon.svg"), @@ -51,7 +53,7 @@ class HealthMetrics { // color: Colors.blue, color: Color(0xff3A3558), icon: Icons.air, - description: "Blood oxygen level indicates how much oxygen your red blood cells are carrying".needTranslation, + description: LocaleKeys.bloodOxygenDescription.tr(), minHealthyValue: 95, maxHealthyValue: 100, svgIcon: "assets/images/smartwatches/bloodoxygen_icon.svg"), diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index d0d5b2f..f09ebd5 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -7,6 +8,7 @@ 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/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; @@ -46,7 +48,7 @@ class _SuggestionsScreenState extends State { context.navigateWithName(AppRoutes.triagePage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one option before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneOptionBeforeProceeding.tr(), ); } } @@ -192,7 +194,7 @@ class _SuggestionsScreenState extends State { children: [ Expanded( child: CollapsingListView( - title: "Suggestions".needTranslation, + title: LocaleKeys.suggestions.tr(), leadingCallback: () => context.pop(), child: viewModel.isSuggestionsLoading ? _buildLoadingShimmer() @@ -225,7 +227,7 @@ class _SuggestionsScreenState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No organs selected'.needTranslation, + LocaleKeys.noOrgansSelected.tr(), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -234,7 +236,7 @@ class _SuggestionsScreenState extends State { ), SizedBox(height: 8.h), Text( - 'Please go back and select organs first'.needTranslation, + LocaleKeys.pleaseGoBackAndSelectOrgansFirst.tr(), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -258,7 +260,7 @@ class _SuggestionsScreenState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -269,7 +271,7 @@ class _SuggestionsScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index d6036c6..b4ba802 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -44,7 +44,7 @@ class _SymptomsSelectorPageState extends State { context.navigateWithName(AppRoutes.riskFactorsPage); } else { dialogService.showErrorBottomSheet( - message: 'Please select at least one symptom before proceeding'.needTranslation, + message: LocaleKeys.pleaseSelectAtLeastOneOptionBeforeProceeding.tr(), ); } } @@ -58,7 +58,7 @@ class _SymptomsSelectorPageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToRestartOrganSelection.tr(), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () => onConfirm(), @@ -79,7 +79,7 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Symptoms Selector".needTranslation, + title: LocaleKeys.symptomsSelector.tr(), leadingCallback: () => _buildConfirmationBottomSheet( context: context, onConfirm: () => { @@ -252,7 +252,7 @@ class _SymptomsSelectorPageState extends State { Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), SizedBox(height: 16.h), Text( - 'No organs selected'.needTranslation, + LocaleKeys.noOrgansSelected.tr(context: context), style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, @@ -261,7 +261,7 @@ class _SymptomsSelectorPageState extends State { ), SizedBox(height: 8.h), Text( - 'Please go back and select organs first'.needTranslation, + LocaleKeys.pleaseGoBackAndSelectOrgansFirst.tr(), textAlign: TextAlign.center, style: TextStyle( fontSize: 14.f, @@ -285,7 +285,7 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: _onPreviousPressed, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, @@ -296,7 +296,7 @@ class _SymptomsSelectorPageState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), onPressed: () => _onNextPressed(viewModel), backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 9d5d884..526136c 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -117,10 +117,9 @@ class _TriagePageState extends State { Lottie.asset(AppAnimations.ambulanceAlert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + LocaleKeys.emergencyTriage.tr(context: context).toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Emergency evidence detected. Please seek medical attention." - .needTranslation + LocaleKeys.emergencyEvidenceDetected.tr(context: context) .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( @@ -159,14 +158,14 @@ class _TriagePageState extends State { final currentQuestion = viewModel.currentTriageQuestion; if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) { dialogService.showErrorBottomSheet( - message: 'No question items available'.needTranslation, + message: LocaleKeys.noQuestionItemsAvailable.tr(context: context), ); return; } // Check if all items have been answered if (!viewModel.areAllTriageItemsAnswered) { - dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation); + dialogService.showErrorBottomSheet(message: LocaleKeys.pleaseAnswerAllQuestionsBeforeProceeding.tr(context: context)); return; } @@ -222,7 +221,7 @@ class _TriagePageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Triage".needTranslation, + title: LocaleKeys.triage.tr(context: context), leadingCallback: () => _showConfirmationBeforeExit(context), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -304,7 +303,7 @@ class _TriagePageState extends State { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context), isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () { @@ -325,7 +324,7 @@ class _TriagePageState extends State { if (viewModel.currentTriageQuestion == null) { return Center( - child: "No question available".needTranslation.toText16(weight: FontWeight.w500), + child: LocaleKeys.noQuestionAvailable.tr(context: context).toText16(weight: FontWeight.w500), ); } @@ -457,7 +456,7 @@ class _TriagePageState extends State { children: [ RichText( text: TextSpan( - text: "Possible symptom: ".needTranslation, + text: LocaleKeys.possibleSymptom.tr(context: context), style: TextStyle( color: AppColors.greyTextColor, fontWeight: FontWeight.w600, @@ -492,7 +491,7 @@ class _TriagePageState extends State { ), children: [ TextSpan( - text: "- Symptoms checker finding score".needTranslation, + text: LocaleKeys.symptomsCheckerFindingScore.tr(context: context), style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w500, @@ -510,7 +509,7 @@ class _TriagePageState extends State { children: [ Expanded( child: CustomButton( - text: "Previous".needTranslation, + text: LocaleKeys.previous.tr(context: context), onPressed: isFirstQuestion ? () {} : _onPreviousPressed, isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), @@ -522,7 +521,7 @@ class _TriagePageState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Next".needTranslation, + text: LocaleKeys.next.tr(context: context), isDisabled: viewModel.isTriageDiagnosisLoading, onPressed: _onNextPressed, backgroundColor: AppColors.primaryRedColor, diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index b438420..9973d54 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -9,6 +10,7 @@ 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/symptoms_checker/symptoms_checker_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/buttons/custom_button.dart'; @@ -149,20 +151,20 @@ class _UserInfoSelectionScreenState extends State { viewModel.selectedWeight == null; // Get display values - String genderText = viewModel.selectedGender ?? "Not set"; + String genderText = viewModel.selectedGender ?? LocaleKeys.notSet.tr(context: context); // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; - String ageText = displayAge != null ? "$displayAge Years" : "Not set"; + String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); String heightText = - viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set"; + viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context); String weightText = - viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : "Not set"; + viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context); return Column( children: [ Expanded( child: CollapsingListView( - title: "Symptoms Checker".needTranslation, + title: LocaleKeys.symptomsChecker.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( @@ -173,7 +175,7 @@ class _UserInfoSelectionScreenState extends State { padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), child: Column( children: [ - "Hello $name, Is your information up to date?".needTranslation.toText16( + LocaleKeys.helloIsYourInformationUpToDate.tr(namedArgs: {'name': name}).toText16( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -181,7 +183,7 @@ class _UserInfoSelectionScreenState extends State { _buildEditInfoTile( context: context, leadingIcon: AppAssets.genderIcon, - title: "Gender".needTranslation, + title: LocaleKeys.gender.tr(context: context), subTitle: genderText, onTap: () { viewModel.setUserInfoPage(0, isSinglePageEdit: true); @@ -193,7 +195,7 @@ class _UserInfoSelectionScreenState extends State { _buildEditInfoTile( context: context, leadingIcon: AppAssets.calendarGrey, - title: "Age".needTranslation, + title: LocaleKeys.age.tr(context: context), subTitle: ageText, iconColor: AppColors.greyTextColor, onTap: () { @@ -206,7 +208,7 @@ class _UserInfoSelectionScreenState extends State { _buildEditInfoTile( context: context, leadingIcon: AppAssets.rulerIcon, - title: "Height".needTranslation, + title: LocaleKeys.height.tr(context: context), subTitle: heightText, onTap: () { viewModel.setUserInfoPage(2, isSinglePageEdit: true); @@ -218,7 +220,7 @@ class _UserInfoSelectionScreenState extends State { _buildEditInfoTile( context: context, leadingIcon: AppAssets.weightScale, - title: "Weight".needTranslation, + title: LocaleKeys.weight.tr(context: context), subTitle: weightText, onTap: () { viewModel.setUserInfoPage(3, isSinglePageEdit: true); @@ -255,7 +257,7 @@ class _UserInfoSelectionScreenState extends State { children: [ Expanded( child: CustomButton( - text: "No, Edit all".needTranslation, + text: LocaleKeys.noEditAll.tr(context: context), icon: AppAssets.edit_icon, iconColor: AppColors.primaryRedColor, onPressed: () { @@ -271,7 +273,7 @@ class _UserInfoSelectionScreenState extends State { SizedBox(width: 12.w), Expanded( child: CustomButton( - text: "Yes, It is".needTranslation, + text: LocaleKeys.yesItIs.tr(context: context), icon: AppAssets.tickIcon, iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, onPressed: hasEmptyFields diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 054108d..fe07186 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -83,7 +83,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ Expanded( child: CollapsingListView( - title: "Select Payment Method".needTranslation, + title: LocaleKeys.selectPaymentMethod.tr(context: context), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -103,9 +103,9 @@ class _AncillaryOrderPaymentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), ], ), SizedBox(width: 8.h), @@ -152,7 +152,7 @@ class _AncillaryOrderPaymentPageState extends State { ], ).toShimmer2(isShow: todoVM.isProcessingPayment), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), ], ), SizedBox(width: 8.h), @@ -210,14 +210,14 @@ class _AncillaryOrderPaymentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), // Amount before tax Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Amount before tax".needTranslation.toText14(isBold: true), + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( amountBeforeTax.toStringAsFixed(2).toText16(isBold: true), AppColors.blackColor, @@ -231,7 +231,7 @@ class _AncillaryOrderPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( taxAmount.toStringAsFixed(2).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, @@ -247,7 +247,7 @@ class _AncillaryOrderPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".needTranslation.toText14(isBold: true), + "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol( widget.totalAmount.toStringAsFixed(2).toText24(isBold: true), AppColors.blackColor, @@ -346,7 +346,7 @@ class _AncillaryOrderPaymentPageState extends State { } void _checkPaymentStatus() { - LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); todoSectionViewModel.checkPaymentStatus( transID: transID, @@ -384,7 +384,7 @@ class _AncillaryOrderPaymentPageState extends State { required String paymentReference, required String paymentMethod, }) { - LoaderBottomSheet.showLoader(loadingText: "Processing payment, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.processingPaymentPleaseWait.tr(context: context)); final user = appState.getAuthenticatedUser(); @@ -426,7 +426,7 @@ class _AncillaryOrderPaymentPageState extends State { required String advanceNumber, required String paymentReference, }) { - LoaderBottomSheet.showLoader(loadingText: "Finalizing payment, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.finalizingPaymentPleaseWait.tr(context: context)); final user = appState.getAuthenticatedUser(); @@ -450,7 +450,7 @@ class _AncillaryOrderPaymentPageState extends State { } void _autoGenerateInvoice() { - LoaderBottomSheet.showLoader(loadingText: "Generating invoice, Please wait...".needTranslation); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingInvoicePleaseWait.tr(context: context)); List selectedProcListAPI = widget.selectedProcedures.map((element) { return { @@ -496,7 +496,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ Row( children: [ - "Here is your invoice #: ".needTranslation.toText14( + LocaleKeys.hereIsYourInvoiceNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -510,7 +510,7 @@ class _AncillaryOrderPaymentPageState extends State { Expanded( child: CustomButton( height: 56.h, - text: LocaleKeys.ok.tr(), + text: LocaleKeys.ok.tr(context: context), onPressed: () { Navigator.pushAndRemoveUntil( context, @@ -528,8 +528,8 @@ class _AncillaryOrderPaymentPageState extends State { ), ], ), - // title: "Payment Completed Successfully".needTranslation, - titleWidget: Utils.getSuccessWidget(loadingText: "Payment Completed Successfully".needTranslation), + // title: LocaleKeys.paymentCompletedSuccessfully.tr(context: context), + titleWidget: Utils.getSuccessWidget(loadingText: LocaleKeys.paymentCompletedSuccessfully.tr(context: context)), isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, @@ -607,7 +607,7 @@ class _AncillaryOrderPaymentPageState extends State { Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Failed to initialize Apple Pay. Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: LocaleKeys.failedToInitializeApplePay.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index b7515af..f604673 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -102,14 +102,14 @@ class _AncillaryOrderDetailsListState extends State { String _getApprovalStatusText(AncillaryOrderProcDetail procedure) { if (procedure.isApprovalRequired == false) { - return "Cash"; + return LocaleKeys.cash.tr(context: context); } else { if (procedure.isApprovalCreated == true && procedure.approvalNo != 0) { - return "Approved"; + return LocaleKeys.approved.tr(context: context); } else if (procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0) { - return "Approval Rejected - Please visit receptionist"; + return LocaleKeys.approvalRejectedPleaseVisitReceptionist.tr(context: context); } else { - return "Sent For Approval"; + return LocaleKeys.sentForApproval.tr(context: context); } } } @@ -135,7 +135,7 @@ class _AncillaryOrderDetailsListState extends State { children: [ Expanded( child: CollapsingListView( - title: "Ancillary Order Details".needTranslation, + title: LocaleKeys.ancillaryOrderDetails.tr(context: context), child: viewModel.isAncillaryDetailsProceduresLoading ? _buildLoadingShimmer().paddingSymmetrical(24.w, 0) : viewModel.patientAncillaryOrderProceduresList.isEmpty @@ -186,7 +186,7 @@ class _AncillaryOrderDetailsListState extends State { ), child: Utils.getNoDataWidget( context, - noDataText: "No Procedures available for the selected order.".needTranslation, + noDataText: LocaleKeys.noProceduresAvailableForSelectedOrder.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -372,7 +372,7 @@ class _AncillaryOrderDetailsListState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Procedures".needTranslation.toText12( + LocaleKeys.procedures.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -385,7 +385,7 @@ class _AncillaryOrderDetailsListState extends State { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - "Total Amount".needTranslation.toText12( + LocaleKeys.totalAmount.tr(context: context).toText12( color: AppColors.textColorLight, fontWeight: FontWeight.w600, ), @@ -535,7 +535,7 @@ class _AncillaryOrderDetailsListState extends State { // ), if (procedure.isCovered == true) AppCustomChipWidget( - labelText: "Covered".needTranslation, + labelText: LocaleKeys.covered.tr(context: context), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ), @@ -551,7 +551,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Price".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.price.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -570,7 +570,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "VAT (15%)".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.vatPercent.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -589,7 +589,7 @@ class _AncillaryOrderDetailsListState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Total".needTranslation.toText10(color: AppColors.textColorLight), + LocaleKeys.total.tr(context: context).toText10(color: AppColors.textColorLight), SizedBox(height: 4.h), Row( children: [ @@ -654,7 +654,7 @@ class _AncillaryOrderDetailsListState extends State { CustomButton( borderWidth: 0, backgroundColor: AppColors.infoLightColor, - text: "Proceed to Payment".needTranslation, + text: LocaleKeys.proceedToPayment.tr(context: context), onPressed: () { // Navigate to payment page with selected procedures Navigator.of(context).push( diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index 0d2d806..161ffaf 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -9,6 +10,7 @@ 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/todo_section/models/resp_models/ancillary_order_list_response_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/presentation/todo_section/ancillary_procedures_details_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -61,7 +63,7 @@ class _ToDoPageState extends State { Widget build(BuildContext context) { appState = getIt.get(); return CollapsingListView( - title: "Ancillary Orders".needTranslation, + title: LocaleKeys.ancillaryOrdersList.tr(context: context), isLeading: true, child: SingleChildScrollView( child: Column( diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index 78d7e3e..aa4d0ba 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -79,7 +79,7 @@ class AncillaryOrdersList extends StatelessWidget { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any ancillary orders yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -187,31 +187,31 @@ class AncillaryOrderCard extends StatelessWidget { if (order.appointmentDate != null || isLoading) AppCustomChipWidget( icon: AppAssets.appointment_calendar_icon, - labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!).needTranslation, + labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!), ).toShimmer2(isShow: isLoading), // Appointment Number if (order.appointmentNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}", ).toShimmer2(isShow: isLoading), // Invoice Number if (order.invoiceNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + labelText: isLoading ? "Invoice: 45678" : LocaleKeys.invoiceWithNumber.tr(namedArgs: {'invoiceNo': '${order.invoiceNo}'}), ).toShimmer2(isShow: isLoading), // Queued Status if (order.isQueued == true || isLoading) AppCustomChipWidget( - labelText: "Queued".needTranslation, + labelText: LocaleKeys.queued.tr(context: context), ).toShimmer2(isShow: isLoading), // Check-in Available Status if (order.isCheckInAllow == true || isLoading) AppCustomChipWidget( - labelText: "Check-in Ready".needTranslation, + labelText: LocaleKeys.checkInReady.tr(context: context), ).toShimmer2(isShow: isLoading), ], ), @@ -225,7 +225,7 @@ class AncillaryOrderCard extends StatelessWidget { if (order.isCheckInAllow == true || isLoading) Expanded( child: CustomButton( - text: "Check In".needTranslation, + text: LocaleKeys.checkIn.tr(context: context), onPressed: () { if (isLoading) { return; @@ -249,7 +249,7 @@ class AncillaryOrderCard extends StatelessWidget { // View Details Button Expanded( child: CustomButton( - text: "View Details".needTranslation, + text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { if (isLoading) { return; diff --git a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart index ba2f94d..2d99aa0 100644 --- a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -7,6 +7,7 @@ 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/todo_section/models/resp_models/ancillary_order_list_response_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:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -73,7 +74,7 @@ class AncillaryProceduresList extends StatelessWidget { ), child: Utils.getNoDataWidget( context, - noDataText: "You don't have any ancillary orders yet.".needTranslation, + noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), isSmallWidget: true, width: 62.w, height: 62.h, @@ -118,7 +119,7 @@ class AncillaryOrderCard extends StatelessWidget { children: [ Row( children: [ - "Order #".needTranslation.toText14( + LocaleKeys.orderNumber.tr(context: context).toText14( color: AppColors.textColorLight, weight: FontWeight.w500, ), @@ -181,31 +182,31 @@ class AncillaryOrderCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.calendar, labelText: - isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation, + isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}", ).toShimmer2(isShow: isLoading), // Appointment Number if (order.appointmentNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}", ).toShimmer2(isShow: isLoading), // Invoice Number if (order.invoiceNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + labelText: isLoading ? "Invoice: 45678" : LocaleKeys.invoiceWithNumber.tr(namedArgs: {'invoiceNo': '${order.invoiceNo}'}), ).toShimmer2(isShow: isLoading), // Queued Status if (order.isQueued == true || isLoading) AppCustomChipWidget( - labelText: "Queued".needTranslation, + labelText: LocaleKeys.queued.tr(context: context), ).toShimmer2(isShow: isLoading), // Check-in Available Status if (order.isCheckInAllow == true || isLoading) AppCustomChipWidget( - labelText: "Check-in Ready".needTranslation, + labelText: LocaleKeys.checkInReady.tr(context: context), ).toShimmer2(isShow: isLoading), ], ), @@ -219,7 +220,7 @@ class AncillaryOrderCard extends StatelessWidget { if (order.isCheckInAllow == true || isLoading) Expanded( child: CustomButton( - text: "Check In".needTranslation, + text: LocaleKeys.checkIn.tr(context: context), onPressed: () { if (isLoading) { return; @@ -243,7 +244,7 @@ class AncillaryOrderCard extends StatelessWidget { // View Details Button Expanded( child: CustomButton( - text: "View Details".needTranslation, + text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { if (isLoading) { return; diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 6ff5cc5..4cfde1b 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'dart:io' show Platform; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/calender_utils_new.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -40,13 +42,13 @@ class BottomSheetUtils{ Future _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async { - showCommonBottomSheetWithoutHeight(providedContext, title: "Set the timer of reminder".needTranslation, child: PrescriptionReminderView( + showCommonBottomSheetWithoutHeight(providedContext, title: LocaleKeys.setTimerOfReminder.tr(), child: PrescriptionReminderView( setReminder: (int value) async { if (!isMultiAllowed!) { if (onMultiDateSuccess == null) { CalenderUtilsNew calendarUtils = CalenderUtilsNew.instance; await calendarUtils.createOrUpdateEvent( - title: title ?? "You have appointment with Dr. ".needTranslation + doctorName, + title: title ?? LocaleKeys.youHaveAppointmentWithDr.tr() + doctorName, description: description ?? "At " + appoDateFormatted + " " + appoTimeFormatted, scheduleDateTime: dateTime, eventId: eventId, diff --git a/lib/widgets/countdown_timer.dart b/lib/widgets/countdown_timer.dart index 165a833..1722ebf 100644 --- a/lib/widgets/countdown_timer.dart +++ b/lib/widgets/countdown_timer.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/generated/locale_keys.g.dart'; Widget buildTime(Duration duration, {bool isHomePage = false}) { String twoDigits(int n) => n.toString().padLeft(2, '0'); @@ -11,9 +13,9 @@ Widget buildTime(Duration duration, {bool isHomePage = false}) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - buildTimeColumn(hours, "Hours".needTranslation), - buildTimeColumn(minutes, "Mins".needTranslation), - buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), + buildTimeColumn(hours, LocaleKeys.hours.tr()), + buildTimeColumn(minutes, LocaleKeys.mins.tr()), + buildTimeColumn(seconds, LocaleKeys.secs.tr(), isLast: true), ], ); } From 5feacfaf27bcb46a34f76abe31ca56a05f502ba2 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 18 Jan 2026 10:45:17 +0300 Subject: [PATCH 46/46] Translation updates --- assets/langs/ar-SA.json | 68 +++++++++++++++++- assets/langs/en-US.json | 70 +++++++++++++++++-- lib/core/utils/utils.dart | 6 +- lib/generated/locale_keys.g.dart | 58 +++++++++++++++ .../active_medication_page.dart | 37 +++++----- .../vital_sign/vital_sign_details_page.dart | 60 +++++++++------- .../water_monitor/water_consumption_page.dart | 22 +++--- .../water_monitor_settings_page.dart | 34 ++++----- .../widgets/cup_bottomsheet_widgets.dart | 8 ++- .../widgets/hydration_tips_widget.dart | 12 ++-- .../widgets/water_action_buttons_widget.dart | 8 ++- .../widgets/water_intake_summary_widget.dart | 20 +++--- lib/widgets/app_language_change.dart | 4 +- lib/widgets/appbar/collapsing_list_view.dart | 16 +++-- lib/widgets/appbar/collapsing_toolbar.dart | 12 ++-- .../date_range_calender.dart | 16 ++--- .../family_files/family_file_add_widget.dart | 2 +- lib/widgets/map/location_map_widget.dart | 4 +- lib/widgets/map/map_utility_screen.dart | 12 ++-- lib/widgets/time_picker_widget.dart | 8 ++- 20 files changed, 346 insertions(+), 131 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index e304710..9c1c962 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -408,7 +408,7 @@ "brand": "العلامة التجارية", "power": "القوة", "diameter": "القطر", - "remarks": "ملاحظات", + "remarks": "ملاحظات: ", "activeMedications": "الأدوية النشطة", "expDate": "تاريخ انتهاء الصلاحية النشط:", "route": "الطريق", @@ -1435,5 +1435,69 @@ "overview": "نظرة عامة", "details": "التفاصيل", "healthy": "صحي", - "warning": "تحذير" + "warning": "تحذير", + "vitalSignDetails": "تفاصيل العلامات الحيوية", + "resultOf": "نتيجة {date}", + "resultOfNoDate": "نتيجة --", + "referenceRangeBetween": "النطاق المرجعي: {low} – {high} {unit}", + "referenceRangeMin": "النطاق المرجعي: ≥ {low} {unit}", + "referenceRangeMax": "النطاق المرجعي: ≤ {high} {unit}", + "noHistoryAvailable": "لا يوجد تاريخ متاح", + "bmiDescription": "مؤشر كتلة الجسم هو قياس يعتمد على الطول والوزن لتقدير دهون الجسم.", + "heightDescription": "يقاس الطول بالسنتيمتر ويستخدم لحساب مؤشر كتلة الجسم وتوصيات الجرعات.", + "weightDescription": "الوزن يساعد في تتبع الصحة العامة والتغذية والتغيرات مع مرور الوقت.", + "bloodPressureDescription": "ضغط الدم يعكس قوة الدم على جدران الشرايين. يظهر كانقباضي/انبساطي.", + "temperatureDescription": "درجة حرارة الجسم تعكس مدى سخونة جسمك وقد تتغير مع العدوى أو الالتهاب.", + "heartRateDescriptionVital": "معدل ضربات القلب يشير إلى عدد نبضات القلب في الدقيقة.", + "respiratoryRateDescription": "معدل التنفس هو عدد الأنفاس المأخوذة في الدقيقة.", + "bmiAdvice": "حافظ على نظام غذائي متوازن ونشاط منتظم. إذا كان مؤشر كتلة جسمك مرتفعًا أو منخفضًا، فكر في استشارة طبيبك.", + "heightAdvice": "لا حاجة لاتخاذ أي إجراء إلا إذا بدا قياسك غير صحيح. قم بتحديثه في زيارتك القادمة.", + "weightAdvice": "راقب تغيرات الوزن. الزيادة أو الخسارة المفاجئة قد تتطلب استشارة طبية.", + "bloodPressureAdvice": "استمر في تتبع ضغط دمك. يجب مناقشة القراءات المرتفعة أو المنخفضة مع طبيبك.", + "temperatureAdvice": "إذا كان لديك حمى مستمرة أو أعراض، اتصل بمقدم الرعاية الصحية.", + "heartRateAdvice": "تتبع اتجاهات معدل ضربات قلبك. إذا شعرت بدوار أو ألم في الصدر، اطلب الرعاية الطبية.", + "respiratoryRateAdvice": "إذا لاحظت ضيقًا في التنفس أو تنفسًا غير طبيعي، اطلب المشورة الطبية.", + "whatShouldIDoNext": "ماذا يجب أن أفعل بعد ذلك؟", + "customizeDrinkCup": "قم بتخصيص كوب مشروبك", + "tipsToStayHydrated": "نصائح للبقاء رطبًا", + "drinkBeforeYouFeelThirsty": "اشرب قبل أن تشعر بالعطش", + "keepRefillableBottleNextToYou": "احتفظ بزجاجة قابلة لإعادة التعبئة بجانبك", + "trackYourDailyIntakeToStayMotivated": "تتبع كمية الماء اليومية للحفاظ على الحافز", + "chooseSparklingWaterInsteadOfSoda": "اختر الماء الفوار بدلاً من الصودا", + "switchCup": "تبديل الكوب", + "plainWater": "ماء عادي", + "yourGoal": "هدفك", + "remaining": "المتبقي", + "hydrationStatus": "حالة الترطيب", + "areYouSureYouWantToCancelAllWaterReminders": "هل أنت متأكد أنك تريد إلغاء جميع تذكيرات الماء؟", + "remindersSet": "تم ضبط التذكيرات!", + "dailyWaterRemindersScheduledAt": "تم جدولة تذكيرات الماء اليومية في:", + "waterConsumption": "استهلاك المياه", + "selectNumberOfReminders": "حدد عدد التذكيرات", + "h2oSettings": "إعدادات H20", + "settingsSavedSuccessfully": "تم حفظ الإعدادات بنجاح", + "yourName": "اسمك", + "ageYears": "العمر (11-120) سنة", + "numberOfRemindersInADay": "عدد التذكيرات في اليوم", + + "medications": "الأدوية", + "someRemarksAboutPrescription": "ستجدون هنا بعض الملاحظات حول الوصفة الطبية", + "notifyMeBeforeConsumptionTime": "أبلغني قبل وقت الاستهلاك", + "noMedicationsToday": "لا أدوية اليوم", + "route": "Route: {route}", + "frequency": "Frequency: {frequency}", + "instruction": "Instruction: {instruction}", + "duration": "Duration: {days}", + "reminders": "تذكيرات", + "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ✅", + "errorWhileSettingCalendar": "حدث خطأ أثناء ضبط التقويم:{error}", + "instructions": "التعليمات", + "requests": "الطلبات", + "thisWeek": "هذا الأسبوع", + "lastMonth": "الشهر الماضي", + "lastSixMonths": "آخر 6 أشهر", + "selectTime": "حدد الوقت", + "pleaseWaitYouWillBeCalledForVitalSigns": "يرجى الانتظار! سيتم استدعاؤك لقياس العلامات الحيوية", + "pleaseVisitRoomForVitalSigns": "يرجى زيارة الغرفة {roomNumber} لقياس العلامات الحيوية", + "pleaseVisitRoomToTheDoctor": "يرجى زيارة الغرفة {roomNumber} لمقابلة الطبيب" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 98ca7ad..5f356ba 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -406,11 +406,8 @@ "brand": "Brand", "power": "Power", "diameter": "Diameter", - "remarks": "Remarks", "activeMedications": "Active Medications", "expDate": "Active Exp Date :", - "route": "Route", - "frequency": "Frequency", "dailyQuantity": "Daily Quantity :", "addReminder": "Add Reminder", "cancelReminder": "Cancel Reminder", @@ -1428,5 +1425,70 @@ "overview": "Overview", "details": "Details", "healthy": "Healthy", - "warning": "Warning" + "warning": "Warning", + "vitalSignDetails": "Vital Sign Details", + "resultOf": "Result of {date}", + "resultOfNoDate": "Result of --", + "referenceRangeBetween": "Reference range: {low} – {high} {unit}", + "referenceRangeMin": "Reference range: ≥ {low} {unit}", + "referenceRangeMax": "Reference range: ≤ {high} {unit}", + "noHistoryAvailable": "No history available", + "bmiDescription": "BMI is a measurement based on height and weight that estimates body fat.", + "heightDescription": "Height is measured in centimeters and is used to calculate BMI and dosage recommendations.", + "weightDescription": "Weight helps track overall health, nutrition, and changes over time.", + "bloodPressureDescription": "Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.", + "temperatureDescription": "Body temperature reflects how hot your body is and may change with infection or inflammation.", + "heartRateDescriptionVital": "Heart rate refers to the number of heart beats per minute.", + "respiratoryRateDescription": "Respiratory rate is the number of breaths taken per minute.", + "bmiAdvice": "Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.", + "heightAdvice": "No action is needed unless your measurement looks incorrect. Update it during your next visit.", + "weightAdvice": "Monitor weight changes. Sudden gain or loss may require medical advice.", + "bloodPressureAdvice": "Keep tracking your blood pressure. High or low readings should be discussed with your doctor.", + "temperatureAdvice": "If you have a persistent fever or symptoms, contact your healthcare provider.", + "heartRateAdvice": "Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.", + "respiratoryRateAdvice": "If you notice shortness of breath or abnormal breathing, seek medical advice.", + "whatShouldIDoNext": "What should I do next?", + "customizeDrinkCup": "Customize your drink cup", + "tipsToStayHydrated": "Tips to stay hydrated", + "drinkBeforeYouFeelThirsty": "Drink before you feel thirsty", + "keepRefillableBottleNextToYou": "Keep a refillable bottle next to you", + "trackYourDailyIntakeToStayMotivated": "Track your daily intake to stay motivated", + "chooseSparklingWaterInsteadOfSoda": "Choose sparkling water instead of soda", + "switchCup": "Switch Cup", + "plainWater": "Plain Water", + "yourGoal": "Your Goal", + "remaining": "Remaining", + "hydrationStatus": "Hydration Status", + "areYouSureYouWantToCancelAllWaterReminders": "Are you sure you want to cancel all water reminders?", + "remindersSet": "Reminders Set!", + "dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:", + "waterConsumption": "Water Consumption", + "selectActivityLevel": "Select Activity Level", + "selectNumberOfReminders": "Select Number of Reminders", + "h2oSettings": "H20 Settings", + "settingsSavedSuccessfully": "Settings saved successfully", + "yourName": "Your Name", + "ageYears": "Age (11-120) yrs", + "numberOfRemindersInADay": "Number of reminders in a day", + "medications": "Medications", + "remarks": "Remarks: ", + "someRemarksAboutPrescription": "some remarks about the prescription will be here", + "notifyMeBeforeConsumptionTime": "Notify me before the consumption time", + "noMedicationsToday": "No medications today", + "route": "Route: {route}", + "frequency": "Frequency: {frequency}", + "instruction": "Instruction: {instruction}", + "duration": "Duration: {days}", + "reminders": "Reminders", + "reminderAddedToCalendar": "Reminder added to calendar ✅", + "errorWhileSettingCalendar": "Error while setting calendar: {error}", + "instructions": "Instructions", + "requests": "Requests", + "thisWeek": "This Week", + "lastMonth": "Last Month", + "lastSixMonths": "Last 6 Months", + "selectTime": "Select Time", + "pleaseWaitYouWillBeCalledForVitalSigns": "Please wait! you will be called for vital signs", + "pleaseVisitRoomForVitalSigns": "Please visit Room {roomNumber} for vital signs", + "pleaseVisitRoomToTheDoctor": "Please visit Room {roomNumber} to the Doctor" } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 8978fcd..e5b0ca4 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -959,11 +959,11 @@ class Utils { static String getCardButtonText(int currentQueueStatus, String roomNumber) { switch (currentQueueStatus) { case 0: - return "Please wait! you will be called for vital signs".needTranslation; + return LocaleKeys.pleaseWaitYouWillBeCalledForVitalSigns.tr(); case 1: - return "Please visit Room $roomNumber for vital signs".needTranslation; + return LocaleKeys.pleaseVisitRoomForVitalSigns.tr(namedArgs: {'roomNumber': roomNumber.toString()}); case 2: - return "Please visit Room $roomNumber to the Doctor".needTranslation; + return LocaleKeys.pleaseVisitRoomToTheDoctor.tr(namedArgs: {'roomNumber': roomNumber.toString()}); } return ""; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index dc0655d..b3b2edf 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1430,5 +1430,63 @@ abstract class LocaleKeys { static const details = 'details'; static const healthy = 'healthy'; static const warning = 'warning'; + static const vitalSignDetails = 'vitalSignDetails'; + static const resultOfNoDate = 'resultOfNoDate'; + static const referenceRangeBetween = 'referenceRangeBetween'; + static const referenceRangeMin = 'referenceRangeMin'; + static const referenceRangeMax = 'referenceRangeMax'; + static const noHistoryAvailable = 'noHistoryAvailable'; + static const bmiDescription = 'bmiDescription'; + static const heightDescription = 'heightDescription'; + static const weightDescription = 'weightDescription'; + static const bloodPressureDescription = 'bloodPressureDescription'; + static const temperatureDescription = 'temperatureDescription'; + static const heartRateDescriptionVital = 'heartRateDescriptionVital'; + static const respiratoryRateDescription = 'respiratoryRateDescription'; + static const bmiAdvice = 'bmiAdvice'; + static const heightAdvice = 'heightAdvice'; + static const weightAdvice = 'weightAdvice'; + static const bloodPressureAdvice = 'bloodPressureAdvice'; + static const temperatureAdvice = 'temperatureAdvice'; + static const heartRateAdvice = 'heartRateAdvice'; + static const respiratoryRateAdvice = 'respiratoryRateAdvice'; + static const whatShouldIDoNext = 'whatShouldIDoNext'; + static const customizeDrinkCup = 'customizeDrinkCup'; + static const tipsToStayHydrated = 'tipsToStayHydrated'; + static const drinkBeforeYouFeelThirsty = 'drinkBeforeYouFeelThirsty'; + static const keepRefillableBottleNextToYou = 'keepRefillableBottleNextToYou'; + static const trackYourDailyIntakeToStayMotivated = 'trackYourDailyIntakeToStayMotivated'; + static const chooseSparklingWaterInsteadOfSoda = 'chooseSparklingWaterInsteadOfSoda'; + static const switchCup = 'switchCup'; + static const plainWater = 'plainWater'; + static const yourGoal = 'yourGoal'; + static const remaining = 'remaining'; + static const hydrationStatus = 'hydrationStatus'; + static const areYouSureYouWantToCancelAllWaterReminders = 'areYouSureYouWantToCancelAllWaterReminders'; + static const remindersSet = 'remindersSet'; + static const dailyWaterRemindersScheduledAt = 'dailyWaterRemindersScheduledAt'; + static const waterConsumption = 'waterConsumption'; + static const selectNumberOfReminders = 'selectNumberOfReminders'; + static const h2oSettings = 'h2oSettings'; + static const settingsSavedSuccessfully = 'settingsSavedSuccessfully'; + static const yourName = 'yourName'; + static const ageYears = 'ageYears'; + static const numberOfRemindersInADay = 'numberOfRemindersInADay'; + static const medications = 'medications'; + static const someRemarksAboutPrescription = 'someRemarksAboutPrescription'; + static const notifyMeBeforeConsumptionTime = 'notifyMeBeforeConsumptionTime'; + static const noMedicationsToday = 'noMedicationsToday'; + static const reminders = 'reminders'; + static const reminderAddedToCalendar = 'reminderAddedToCalendar'; + static const errorWhileSettingCalendar = 'errorWhileSettingCalendar'; + static const instructions = 'instructions'; + static const requests = 'requests'; + static const thisWeek = 'thisWeek'; + static const lastMonth = 'lastMonth'; + static const lastSixMonths = 'lastSixMonths'; + static const selectTime = 'selectTime'; + static const pleaseWaitYouWillBeCalledForVitalSigns = 'pleaseWaitYouWillBeCalledForVitalSigns'; + static const pleaseVisitRoomForVitalSigns = 'pleaseVisitRoomForVitalSigns'; + static const pleaseVisitRoomToTheDoctor = 'pleaseVisitRoomToTheDoctor'; } diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index d0720fb..14da5b0 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -134,7 +134,7 @@ class _ActiveMedicationPageState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Active Medications".needTranslation, + Text(LocaleKeys.activeMedications.tr(), style: TextStyle( color: AppColors.textColor, fontSize: 27.f, @@ -191,7 +191,7 @@ class _ActiveMedicationPageState extends State { ], ), ), - Text("Medications".needTranslation, + Text(LocaleKeys.medications.tr(), style: TextStyle( color: AppColors.primaryRedBorderColor, fontSize: 12.f, @@ -253,8 +253,7 @@ class _ActiveMedicationPageState extends State { text: TextSpan( children: [ TextSpan( - text: "Remarks: " - .needTranslation, + text: LocaleKeys.remarks.tr(), style: TextStyle( color: AppColors.textColor, @@ -265,8 +264,7 @@ class _ActiveMedicationPageState extends State { ), TextSpan( text: - "some remarks about the prescription will be here" - .needTranslation, + LocaleKeys.someRemarksAboutPrescription.tr(), style: TextStyle( color: AppColors .lightGreyTextColor, @@ -312,8 +310,7 @@ class _ActiveMedicationPageState extends State { CrossAxisAlignment.start, children: [ Text( - "Set Reminder" - .needTranslation, + LocaleKeys.setReminder.tr(), style: TextStyle( fontSize: 14.f, fontWeight: @@ -321,8 +318,7 @@ class _ActiveMedicationPageState extends State { color: AppColors .textColor)), Text( - "Notify me before the consumption time" - .needTranslation, + LocaleKeys.notifyMeBeforeConsumptionTime.tr(), style: TextStyle( fontSize: 12.f, color: AppColors @@ -346,7 +342,7 @@ class _ActiveMedicationPageState extends State { : Utils.getNoDataWidget( context, noDataText: - "No medications today".needTranslation, + LocaleKeys.noMedicationsToday.tr(), ), ), ), @@ -397,17 +393,16 @@ class _ActiveMedicationPageState extends State { children: [ AppCustomChipWidget( labelText: - "Route: ${med.route}".needTranslation), + LocaleKeys.route.tr(namedArgs: {'route': med.route ?? ''})), AppCustomChipWidget( labelText: - "Frequency: ${med.frequency}".needTranslation), + LocaleKeys.frequency.tr(namedArgs: {'frequency': med.frequency ?? ''})), AppCustomChipWidget( labelText: - "Daily Dose: ${med.doseDailyQuantity}" - .needTranslation), + LocaleKeys.instruction.tr(namedArgs: {'instruction': med.doseDailyQuantity?.toString() ?? ''})), AppCustomChipWidget( labelText: - "Duration: ${med.days}".needTranslation), + LocaleKeys.duration.tr(namedArgs: {'days': med.days.toString() ?? ''})), ], ), ], @@ -419,7 +414,7 @@ class _ActiveMedicationPageState extends State { child: Row(children: [ Expanded( child: CustomButton( - text: "Check Availability".needTranslation, + text: LocaleKeys.checkAvailability.tr(), fontSize: 13.f, onPressed: () {}, backgroundColor: AppColors.secondaryLightRedColor, @@ -430,7 +425,7 @@ class _ActiveMedicationPageState extends State { SizedBox(width: 12.h), Expanded( child: CustomButton( - text: "Read Instructions".needTranslation, + text: LocaleKeys.readInstructions.tr(), fontSize: 13.f, onPressed: () {})), ]), @@ -519,7 +514,7 @@ class _ActiveMedicationPageState extends State { MainAxisAlignment.spaceBetween, children: [ Text( - "Reminders".needTranslation, + LocaleKeys.reminders.tr(), style: TextStyle( fontSize: 20.f, fontWeight: FontWeight.w600, @@ -988,11 +983,11 @@ class _ReminderTimerDialogState extends State { route: widget.med.route ?? "", ); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Reminder added to calendar ✅".needTranslation)), + SnackBar(content: Text(LocaleKeys.reminderAddedToCalendar.tr())), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error while setting calendar: $e".needTranslation)), + SnackBar(content: Text(LocaleKeys.errorWhileSettingCalendar.tr(namedArgs: {'error': e.toString()}))), ); } Navigator.pop(context); diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index f632502..980357d 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -1,3 +1,4 @@ +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'; @@ -9,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_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/graph/custom_graph.dart'; @@ -62,7 +64,7 @@ class _VitalSignDetailsPageState extends State { @override Widget build(BuildContext context) { return CollapsingListView( - title: 'Vital Sign Details'.needTranslation, + title: LocaleKeys.vitalSignDetails.tr(context: context), child: Consumer( builder: (context, viewModel, child) { final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; @@ -128,8 +130,8 @@ class _VitalSignDetailsPageState extends State { ), SizedBox(height: 8.h), (latestDate != null - ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) - : ('Result of --'.needTranslation)) + ? LocaleKeys.resultOf.tr(namedArgs: {'date': latestDate.toString().split(' ').first}) + : LocaleKeys.resultOfNoDate.tr(context: context)) .toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), ], ), @@ -185,13 +187,23 @@ class _VitalSignDetailsPageState extends State { String _referenceText(BuildContext context) { if (args.low != null && args.high != null) { - return 'Reference range: ${args.low} – ${args.high} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeBetween.tr(namedArgs: { + 'low': args.low.toString(), + 'high': args.high.toString(), + 'unit': args.unit ?? '' + }); } if (args.low != null) { - return 'Reference range: ≥ ${args.low} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeMin.tr(namedArgs: { + 'low': args.low.toString(), + 'unit': args.unit ?? '' + }); } if (args.high != null) { - return 'Reference range: ≤ ${args.high} ${args.unit}'.needTranslation; + return LocaleKeys.referenceRangeMax.tr(namedArgs: { + 'high': args.high.toString(), + 'unit': args.unit ?? '' + }); } return ''; } @@ -208,7 +220,7 @@ class _VitalSignDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), _descriptionText(context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), ], ), @@ -243,7 +255,7 @@ class _VitalSignDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - _isGraphVisible ? 'History flowchart'.needTranslation : 'History'.needTranslation, + _isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -288,7 +300,7 @@ class _VitalSignDetailsPageState extends State { ).paddingOnly(bottom: _isGraphVisible ? 16.h : 24.h), if (history.isEmpty) - Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) + Utils.getNoDataWidget(context, noDataText: LocaleKeys.noHistoryAvailable.tr(context: context), isSmallWidget: true) else if (_isGraphVisible) _buildHistoryGraph(history, secondaryHistory: secondaryHistory) else @@ -649,38 +661,38 @@ class _VitalSignDetailsPageState extends State { String _descriptionText(BuildContext context) { switch (args.metric) { case VitalSignMetric.bmi: - return 'BMI is a measurement based on height and weight that estimates body fat.'.needTranslation; + return LocaleKeys.bmiDescription.tr(context: context); case VitalSignMetric.height: - return 'Height is measured in centimeters and is used to calculate BMI and dosage recommendations.'.needTranslation; + return LocaleKeys.heightDescription.tr(context: context); case VitalSignMetric.weight: - return 'Weight helps track overall health, nutrition, and changes over time.'.needTranslation; + return LocaleKeys.weightDescription.tr(context: context); case VitalSignMetric.bloodPressure: - return 'Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.'.needTranslation; + return LocaleKeys.bloodPressureDescription.tr(context: context); case VitalSignMetric.temperature: - return 'Body temperature reflects how hot your body is and may change with infection or inflammation.'.needTranslation; + return LocaleKeys.temperatureDescription.tr(context: context); case VitalSignMetric.heartRate: - return 'Heart rate refers to the number of heart beats per minute.'.needTranslation; + return LocaleKeys.heartRateDescriptionVital.tr(context: context); case VitalSignMetric.respiratoryRate: - return 'Respiratory rate is the number of breaths taken per minute.'.needTranslation; + return LocaleKeys.respiratoryRateDescription.tr(context: context); } } String _nextStepsText(BuildContext context) { switch (args.metric) { case VitalSignMetric.bmi: - return 'Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.'.needTranslation; + return LocaleKeys.bmiAdvice.tr(context: context); case VitalSignMetric.height: - return 'No action is needed unless your measurement looks incorrect. Update it during your next visit.'.needTranslation; + return LocaleKeys.heightAdvice.tr(context: context); case VitalSignMetric.weight: - return 'Monitor weight changes. Sudden gain or loss may require medical advice.'.needTranslation; + return LocaleKeys.weightAdvice.tr(context: context); case VitalSignMetric.bloodPressure: - return 'Keep tracking your blood pressure. High or low readings should be discussed with your doctor.'.needTranslation; + return LocaleKeys.bloodPressureAdvice.tr(context: context); case VitalSignMetric.temperature: - return 'If you have a persistent fever or symptoms, contact your healthcare provider.'.needTranslation; + return LocaleKeys.temperatureAdvice.tr(context: context); case VitalSignMetric.heartRate: - return 'Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.'.needTranslation; + return LocaleKeys.heartRateAdvice.tr(context: context); case VitalSignMetric.respiratoryRate: - return 'If you notice shortness of breath or abnormal breathing, seek medical advice.'.needTranslation; + return LocaleKeys.respiratoryRateAdvice.tr(context: context); } } @@ -695,7 +707,7 @@ class _VitalSignDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 'What should I do next?'.needTranslation.toText16(weight: FontWeight.w600), + LocaleKeys.whatShouldIDoNext.tr(context: context).toText16(weight: FontWeight.w600), SizedBox(height: 8.h), _nextStepsText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), ], diff --git a/lib/presentation/water_monitor/water_consumption_page.dart b/lib/presentation/water_monitor/water_consumption_page.dart index 2bd429c..54dace5 100644 --- a/lib/presentation/water_monitor/water_consumption_page.dart +++ b/lib/presentation/water_monitor/water_consumption_page.dart @@ -1,3 +1,4 @@ +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'; @@ -9,6 +10,7 @@ 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/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/hydration_tips_widget.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_intake_summary_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -100,7 +102,7 @@ class _WaterConsumptionPageState extends State { children: [ Row( children: [ - "History".needTranslation.toText16(isBold: true), + LocaleKeys.history.tr(context: context).toText16(isBold: true), SizedBox(width: 8.w), InkWell( onTap: () => _showHistoryDurationBottomsheet(context, viewModel), @@ -604,7 +606,7 @@ class _WaterConsumptionPageState extends State { final dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -634,7 +636,7 @@ class _WaterConsumptionPageState extends State { void _showHistoryDurationBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Duration".needTranslation, + title: LocaleKeys.selectDuration.tr(context: context), items: viewModel.durationFilters, selectedValue: viewModel.selectedDurationFilter, onSelected: viewModel.setFilterDuration, @@ -655,10 +657,10 @@ class _WaterConsumptionPageState extends State { /// Show confirmation bottom sheet before cancelling reminders void _showCancelReminderConfirmation(WaterMonitorViewModel viewModel) { showCommonBottomSheetWithoutHeight( - title: 'Notice'.needTranslation, + title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: "Are you sure you want to cancel all water reminders?".needTranslation, + loadingText: LocaleKeys.areYouSureYouWantToCancelAllWaterReminders.tr(context: context), isShowActionButtons: true, onCancelTap: () { Navigator.pop(context); @@ -694,7 +696,7 @@ class _WaterConsumptionPageState extends State { /// Show bottom sheet with scheduled reminder times void _showReminderScheduledDialog(List times) { showCommonBottomSheetWithoutHeight( - title: 'Reminders Set!'.needTranslation, + title: LocaleKeys.remindersSet.tr(context: context), context, isCloseButtonVisible: false, isDismissible: false, @@ -703,7 +705,7 @@ class _WaterConsumptionPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.getSuccessWidget(loadingText: 'Daily water reminders scheduled at:'.needTranslation), + Utils.getSuccessWidget(loadingText: LocaleKeys.dailyWaterRemindersScheduledAt.tr(context: context)), SizedBox(height: 16.h), Wrap( spacing: 8.w, @@ -728,7 +730,7 @@ class _WaterConsumptionPageState extends State { Expanded( child: CustomButton( height: 56.h, - text: 'OK'.needTranslation, + text: LocaleKeys.ok.tr(context: context), onPressed: () => Navigator.of(context).pop(), textColor: AppColors.whiteColor, ), @@ -792,7 +794,7 @@ class _WaterConsumptionPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Water Consumption".needTranslation, + title: LocaleKeys.waterConsumption.tr(context: context), bottomChild: Consumer( builder: (context, viewModel, child) { return Container( @@ -804,7 +806,7 @@ class _WaterConsumptionPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: viewModel.isWaterReminderEnabled ? "Cancel Reminders".needTranslation : "Set Reminder".needTranslation, + text: viewModel.isWaterReminderEnabled ? LocaleKeys.cancelReminder.tr(context: context) : LocaleKeys.setReminder.tr(context: context), textColor: viewModel.isWaterReminderEnabled ? AppColors.errorColor : AppColors.successColor, backgroundColor: viewModel.isWaterReminderEnabled ? AppColors.errorColor.withValues(alpha: 0.1) : AppColors.successLightBgColor, onPressed: () => _handleReminderButtonTap(viewModel), diff --git a/lib/presentation/water_monitor/water_monitor_settings_page.dart b/lib/presentation/water_monitor/water_monitor_settings_page.dart index 302940c..a3c82c0 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_page.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; @@ -69,7 +71,7 @@ class _WaterMonitorSettingsPageState extends State { bool useUpperCase = false, }) { dialogService.showFamilyBottomSheetWithoutHWithChild( - label: title.needTranslation, + label: title, message: "", child: Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), @@ -99,7 +101,7 @@ class _WaterMonitorSettingsPageState extends State { void _showGenderSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Gender".needTranslation, + title: LocaleKeys.selectGender.tr(context: context), items: viewModel.genderOptions, selectedValue: viewModel.selectedGender, onSelected: viewModel.setGender, @@ -109,7 +111,7 @@ class _WaterMonitorSettingsPageState extends State { void _showHeightUnitSelectionBottomSheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.heightUnits, selectedValue: viewModel.selectedHeightUnit, onSelected: viewModel.setHeightUnit, @@ -120,7 +122,7 @@ class _WaterMonitorSettingsPageState extends State { void _showWeightUnitSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Unit".needTranslation, + title: LocaleKeys.selectUnit.tr(context: context), items: viewModel.weightUnits, selectedValue: viewModel.selectedWeightUnit, onSelected: viewModel.setWeightUnit, @@ -131,7 +133,7 @@ class _WaterMonitorSettingsPageState extends State { void _showActivityLevelSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Activity Level".needTranslation, + title: LocaleKeys.selectActivityLevel.tr(context: context), items: viewModel.activityLevels, selectedValue: viewModel.selectedActivityLevel, onSelected: viewModel.setActivityLevel, @@ -141,7 +143,7 @@ class _WaterMonitorSettingsPageState extends State { void _showNumberOfRemindersSelectionBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) { _showSelectionBottomSheet( context: context, - title: "Select Number of Reminders".needTranslation, + title: LocaleKeys.selectNumberOfReminders.tr(context: context), items: viewModel.reminderOptions, selectedValue: viewModel.selectedNumberOfReminders, onSelected: viewModel.setNumberOfReminders, @@ -256,7 +258,7 @@ class _WaterMonitorSettingsPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "H20 Settings".needTranslation, + title: LocaleKeys.h2oSettings.tr(context: context), bottomChild: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -266,7 +268,7 @@ class _WaterMonitorSettingsPageState extends State { child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( - text: "Save".needTranslation, + text: LocaleKeys.save.tr(context: context), onPressed: () async { final success = await viewModel.saveSettings(); if (!success && viewModel.validationError != null) { @@ -277,7 +279,7 @@ class _WaterMonitorSettingsPageState extends State { showCommonBottomSheetWithoutHeight( context, child: Utils.getSuccessWidget( - loadingText: "Settings saved successfully".needTranslation, + loadingText: LocaleKeys.settingsSavedSuccessfully.tr(context: context), ), callBackFunc: () {}, isCloseButtonVisible: false, @@ -299,18 +301,18 @@ class _WaterMonitorSettingsPageState extends State { children: [ _buildSettingsRow( icon: AppAssets.profileIcon, - label: "Your Name".needTranslation, + label: LocaleKeys.yourName.tr(context: context), inputField: _buildTextField(viewModel.nameController, 'Guest'), ), _buildSettingsRow( icon: AppAssets.genderIcon, - label: "Select Gender".needTranslation, + label: LocaleKeys.selectGender.tr(context: context), value: viewModel.selectedGender, onRowTap: () => _showGenderSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.calendarGrey, - label: "Age (11-120) yrs".needTranslation, + label: LocaleKeys.ageYears.tr(context: context), inputField: _buildTextField( viewModel.ageController, '20', @@ -319,7 +321,7 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.heightIcon, - label: "Height".needTranslation, + label: LocaleKeys.height.tr(context: context), inputField: _buildTextField( viewModel.heightController, '175', @@ -330,7 +332,7 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.weightScaleIcon, - label: "Weight".needTranslation, + label: LocaleKeys.weight.tr(context: context), inputField: _buildTextField( viewModel.weightController, '75', @@ -341,13 +343,13 @@ class _WaterMonitorSettingsPageState extends State { ), _buildSettingsRow( icon: AppAssets.dumbellIcon, - label: "Activity Level".needTranslation, + label: LocaleKeys.activityLevel.tr(context: context), value: viewModel.selectedActivityLevel, onRowTap: () => _showActivityLevelSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.notificationIconGrey, - label: "Number of reminders in a day".needTranslation, + label: LocaleKeys.numberOfRemindersInADay.tr(context: context), value: viewModel.selectedNumberOfReminders, onRowTap: () => _showNumberOfRemindersSelectionBottomsheet(context, viewModel), showDivider: false, diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 4ffa30d..4ee6170 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -7,6 +8,7 @@ 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/water_monitor/models/water_cup_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_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:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -143,7 +145,7 @@ class SwitchCupBottomSheet extends StatelessWidget { child: Center(child: Utils.buildSvgWithAssets(icon: AppAssets.cupAdd, height: 30.h, width: 42.w)), ), SizedBox(height: 4.h), - 'Add'.needTranslation.toText10(weight: FontWeight.w500), + LocaleKeys.add.tr(context: context).toText10(weight: FontWeight.w500), ], ), ); @@ -157,7 +159,7 @@ void showCustomizeCupBottomSheet(BuildContext context, {WaterCupModel? cupToEdit titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Customize your drink cup".needTranslation.toText20(weight: FontWeight.w600), + LocaleKeys.customizeDrinkCup.tr(context: context).toText20(weight: FontWeight.w600), ], ), child: CustomizeCupBottomSheet(cupToEdit: cupToEdit), @@ -294,7 +296,7 @@ class _CustomizeCupBottomSheetState extends State { SizedBox(height: 24.h), CustomButton( - text: 'Select'.needTranslation, + text: LocaleKeys.select.tr(context: context), onPressed: () { final newCup = WaterCupModel( id: widget.cupToEdit?.id ?? Uuid().v4(), diff --git a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart index df55886..7c49550 100644 --- a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart +++ b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class HydrationTipsWidget extends StatelessWidget { @@ -30,26 +32,26 @@ class HydrationTipsWidget extends StatelessWidget { height: 24.h, ), SizedBox(width: 8.w), - "Tips to stay hydrated".needTranslation.toText16(isBold: true), + LocaleKeys.tipsToStayHydrated.tr(context: context).toText16(isBold: true), ], ), SizedBox(height: 8.h), - " • ${"Drink before you feel thirsty"}".needTranslation.toText12( + " • ${LocaleKeys.drinkBeforeYouFeelThirsty.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Keep a refillable bottle next to you"}".needTranslation.toText12( + " • ${LocaleKeys.keepRefillableBottleNextToYou.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Track your daily intake to stay motivated"}".needTranslation.toText12( + " • ${LocaleKeys.trackYourDailyIntakeToStayMotivated.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), SizedBox(height: 4.h), - " • ${"Choose sparkling water instead of soda"}".needTranslation.toText12( + " • ${LocaleKeys.chooseSparklingWaterInsteadOfSoda.tr(context: context)}".toText12( fontWeight: FontWeight.w500, color: AppColors.textColorLight, ), diff --git a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart index 2359904..115dd21 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -6,6 +7,7 @@ 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/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; @@ -83,19 +85,19 @@ class WaterActionButtonsWidget extends StatelessWidget { context: context, onTap: () => showSwitchCupBottomSheet(context), overlayWidget: AppAssets.refreshIcon, - title: "Switch Cup".needTranslation, + title: LocaleKeys.switchCup.tr(context: context), icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), _buildActionButton( context: context, onTap: () async {}, - title: "Plain Water".needTranslation, + title: LocaleKeys.plainWater.tr(context: context), icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), _buildActionButton( context: context, onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsPage), - title: "Settings".needTranslation, + title: LocaleKeys.settings.tr(context: context), icon: Icon( Icons.settings, color: AppColors.blueColor, diff --git a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart index 137f6a3..ec796ab 100644 --- a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_action_buttons_widget.dart'; import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_bottle_widget.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,7 +53,6 @@ class WaterIntakeSummaryWidget extends StatelessWidget { if (!vm.nextDrinkTime.toLowerCase().contains('goal achieved')) // Show "Tomorrow" if nextDrinkTime contains "tomorrow", otherwise "Next Drink Time" (vm.nextDrinkTime.toLowerCase().contains('tomorrow') ? "Tomorrow" : "Next Drink Time") - .needTranslation .toText18(weight: FontWeight.w600, color: AppColors.textColor), // Extract only time if "tomorrow" is present, otherwise show as is @@ -61,14 +62,17 @@ class WaterIntakeSummaryWidget extends StatelessWidget { .toText32(weight: FontWeight.w600, color: AppColors.blueColor), SizedBox(height: 12.h), - _buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"), - SizedBox(height: 8.h), - _buildStatusColumn(title: "Remaining".needTranslation, subTitle: "${remaining}ml"), - SizedBox(height: 8.h), - _buildStatusColumn(title: "Completed".needTranslation, subTitle: completedPercent, subTitleColor: AppColors.successColor), - SizedBox(height: 8.h), + Row( + children: [ + _buildStatusColumn(title: LocaleKeys.yourGoal.tr(context: context), subTitle: "${goalMl}ml"), + SizedBox(width: 16.w), + _buildStatusColumn(title: LocaleKeys.remaining.tr(context: context), subTitle: "${remaining}ml"), + SizedBox(width: 16.w), + _buildStatusColumn(title: LocaleKeys.completed.tr(context: context), subTitle: completedPercent, subTitleColor: AppColors.successColor), + ], + ), _buildStatusColumn( - title: "Hydration Status".needTranslation, + title: LocaleKeys.hydrationStatus.tr(context: context), subTitle: vm.hydrationStatus, subTitleColor: vm.hydrationStatusColor, ), diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index de9cda5..67a52db 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -41,9 +41,9 @@ class _AppLanguageChangeState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: Column( children: [ - languageItem("English".needTranslation, "en"), + languageItem("English", "en"), 1.divider, - languageItem("العربية".needTranslation, "ar"), + languageItem("العربية", "ar"), ], ), ), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 0580776..897d241 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -1,4 +1,5 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +9,7 @@ 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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import '../../core/dependencies.dart'; @@ -250,7 +252,7 @@ class _ScrollAnimatedTitleState extends State { @override Widget build(BuildContext context) { - final isRtl = Directionality.of(context) == TextDirection.rtl; + final isRtl = Directionality.of(context) == TextDirection.RTL; return Container( height: (widget.preferredSize.height - _fontSize / 2).h, alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), @@ -269,12 +271,12 @@ class _ScrollAnimatedTitleState extends State { ), ).expanded, ...[ - if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), - if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), - if (widget.sendEmail != null) actionButton(context, t, title: "Send Email".needTranslation, icon: AppAssets.email).onPress(widget.sendEmail!), + if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), if (widget.trailing != null) widget.trailing!, ] diff --git a/lib/widgets/appbar/collapsing_toolbar.dart b/lib/widgets/appbar/collapsing_toolbar.dart index 87cf15a..8bf1e4b 100644 --- a/lib/widgets/appbar/collapsing_toolbar.dart +++ b/lib/widgets/appbar/collapsing_toolbar.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import '../../core/dependencies.dart'; @@ -140,11 +142,11 @@ class _CollapsingToolbarState extends State { color: AppColors.blackColor, letterSpacing: -0.5), ).expanded, - if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), - if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: LocaleKeys.report.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!).paddingOnly(right: 24), if (widget.trailing != null) widget.trailing!, ], diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index debc069..89725f3 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -71,7 +71,7 @@ class _DateRangeSelectorState extends State { children: [ fromDateComponent(), Text( - LocaleKeys.to.tr(), + LocaleKeys.to.tr(context: context), style: TextStyle( color: AppColors.calenderTextColor, fontSize: 14.h, @@ -168,7 +168,7 @@ class _DateRangeSelectorState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: LocaleKeys.cancel.tr(context: context), onPressed: () { _calendarController.selectedRange = null; _calendarController.selectedDate = null; @@ -192,7 +192,7 @@ class _DateRangeSelectorState extends State { ), Expanded( child: CustomButton( - text: LocaleKeys.search.tr(), + text: LocaleKeys.search.tr(context: context), onPressed: () { Navigator.of(context).pop(); widget.onRangeSelected(model.fromDate, model.toDate); @@ -216,7 +216,7 @@ class _DateRangeSelectorState extends State { fromDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.startDate.tr(), + return displayDate(LocaleKeys.startDate.tr(context: context), model.getDateString(model.fromDate), model.fromDate == null); }, ); @@ -225,7 +225,7 @@ class _DateRangeSelectorState extends State { toDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.endDate.tr(), + return displayDate(LocaleKeys.endDate.tr(context: context), model.getDateString(model.toDate), model.toDate == null); }, ); @@ -270,7 +270,7 @@ class _DateRangeSelectorState extends State { spacing: 8.h, children: [ AppCustomChipWidget( - labelText: "This Week".needTranslation, + labelText: LocaleKeys.thisWeek.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, @@ -288,7 +288,7 @@ class _DateRangeSelectorState extends State { model.calculateDatesFromRange(); }), AppCustomChipWidget( - labelText: "Last Month".needTranslation, + labelText: LocaleKeys.lastMonth.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, @@ -306,7 +306,7 @@ class _DateRangeSelectorState extends State { model.calculateDatesFromRange(); }), AppCustomChipWidget( - labelText: "Last 6 Months".needTranslation, + labelText: LocaleKeys.lastSixMonths.tr(context: context), backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index 4840ba7..abc529b 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -77,7 +77,7 @@ class FamilyFileAddWidget extends StatelessWidget { ), SizedBox(height: 20.h), CustomButton( - text: "Verify the member".needTranslation, + text: LocaleKeys.pleaseVerify.tr(context: context), onPressed: () { FocusScope.of(context).unfocus(); if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index c0eb431..2a1ea65 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_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/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:maps_launcher/maps_launcher.dart'; @@ -154,7 +156,7 @@ class LocationMapWidget extends StatelessWidget { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - text: "Get Directions".needTranslation, + text: LocaleKeys.getDirections.tr(context: context), onPressed: onDirectionsTap ?? _defaultLaunchDirections, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), borderColor: AppColors.textColor.withValues(alpha: 0.01), diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index 19823da..cb3eb8e 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -5,6 +6,7 @@ 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/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -126,14 +128,14 @@ class MapUtilityScreen extends StatelessWidget { weight: FontWeight.w600, color: AppColors.textColor, ), - subTitleString.needTranslation.toText12( + subTitleString.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) ], ), CustomButton( - text: confirmButtonString.needTranslation, + text: confirmButtonString, onPressed: () { if (onSubmitted != null) { onSubmitted!(); @@ -172,8 +174,8 @@ class MapUtilityScreen extends StatelessWidget { return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, + labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), + hintText: LocaleKeys.enterPickupLocation.tr(context: context), controller: TextEditingController( text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), @@ -203,7 +205,7 @@ class MapUtilityScreen extends StatelessWidget { openLocationInputBottomSheet(BuildContext context) { context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( - title: "".needTranslation, + title: "", context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, diff --git a/lib/widgets/time_picker_widget.dart b/lib/widgets/time_picker_widget.dart index 71d9ab2..bc996de 100644 --- a/lib/widgets/time_picker_widget.dart +++ b/lib/widgets/time_picker_widget.dart @@ -1,9 +1,11 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.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'; @@ -150,7 +152,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Select Time".needTranslation.toText18( + LocaleKeys.selectTime.tr(context: context).toText18( weight: FontWeight.w600, color: AppColors.textColor, ), @@ -318,7 +320,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { Expanded( child: CustomButton( height: 56.h, - text: "Cancel".needTranslation, + text: LocaleKeys.cancel.tr(context: context), onPressed: () => Navigator.pop(context), textColor: AppColors.textColor, backgroundColor: AppColors.greyColor, @@ -329,7 +331,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { Expanded( child: CustomButton( height: 56.h, - text: "Confirm".needTranslation, + text: LocaleKeys.confirm.tr(context: context), onPressed: () { Navigator.pop(context, _getCurrentTime()); },