From e8319a0d3f1b0988285c33365c04bad3ab8673aa Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Wed, 8 Oct 2025 12:26:23 +0300 Subject: [PATCH 1/5] 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), ], -- 2.30.2 From de8c7bc60523fcd041507130e9de461fd239280e Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 21 Oct 2025 10:51:45 +0300 Subject: [PATCH 2/5] 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(', ')}')), +// ); +// } +// } +// } +// +// +// -- 2.30.2 From eae16eec44f5a702b59801f4c36209279f780f0b Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Mon, 17 Nov 2025 09:38:20 +0300 Subject: [PATCH 3/5] 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); } -- 2.30.2 From 77242825bbfd2653b310de8836409303d1fd91c3 Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 18 Nov 2025 10:54:57 +0300 Subject: [PATCH 4/5] 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 { + + -- 2.30.2 From e17d3cde87a0fe0a2060b8c98531ca0c8eff074b Mon Sep 17 00:00:00 2001 From: "Fatimah.Alshammari" Date: Tue, 18 Nov 2025 11:19:15 +0300 Subject: [PATCH 5/5] 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( -- 2.30.2