diff --git a/assets/images/svg/bfc.svg b/assets/images/svg/bfc.svg
new file mode 100644
index 00000000..a6d2ef3a
--- /dev/null
+++ b/assets/images/svg/bfc.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bmi1.svg b/assets/images/svg/bmi1.svg
new file mode 100644
index 00000000..c957dc8e
--- /dev/null
+++ b/assets/images/svg/bmi1.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bmr1.svg b/assets/images/svg/bmr1.svg
new file mode 100644
index 00000000..7e403a81
--- /dev/null
+++ b/assets/images/svg/bmr1.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/cloudy_weather.svg b/assets/images/svg/cloudy_weather.svg
new file mode 100644
index 00000000..bc7d54bf
--- /dev/null
+++ b/assets/images/svg/cloudy_weather.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/images/svg/ddc.svg b/assets/images/svg/ddc.svg
new file mode 100644
index 00000000..f671baf8
--- /dev/null
+++ b/assets/images/svg/ddc.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/ibwc.svg b/assets/images/svg/ibwc.svg
new file mode 100644
index 00000000..acfba187
--- /dev/null
+++ b/assets/images/svg/ibwc.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/op.svg b/assets/images/svg/op.svg
new file mode 100644
index 00000000..ff400de8
--- /dev/null
+++ b/assets/images/svg/op.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/whitecalender.svg b/assets/images/svg/whitecalender.svg
new file mode 100644
index 00000000..ff400de8
--- /dev/null
+++ b/assets/images/svg/whitecalender.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json
index 5df2dcbd..3827d919 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -1177,14 +1177,22 @@
"monitorCholesterolLevels": "راقب مستويات الكوليسترول، وقيّم مخاطر صحة القلب، واتخذ خطوات استباقية للرفاهية.",
"triglyceridesFatBlood": "الدهون الثلاثية في الدم",
"understandTriglyceridesImpact": "افهم تأثير الدهون الثلاثية على صحة القلب مع رؤى مخصصة وتوصيات الخبراء.",
- "bmiCalculator": "حاسبة\nمؤشر كتلة الجسم",
- "caloriesCalculator": "حاسبة\nالسعرات الحرارية",
- "bmrCalculator": "حاسبة\nمعدل الأيض الأساسي",
- "idealBodyWeight": "الوزن المثالي\nللجسم",
- "bodyFatCalculator": "حاسبة\nدهون الجسم",
- "carbsProteinFat": "الكربوهيدرات\nالبروتين والدهون",
- "ovulationPeriod": "فترة\nالإباضة",
- "deliveryDueDate": "تاريخ الولادة\nالمتوقع",
+ "bmiCalculator": "حاسبة مؤشر كتلة الجسم",
+ "bmiCalculatorDesc": "احسب مؤشر كتلة جسمك لفهم نطاق وزنك الصحي.",
+ "caloriesCalculator": "حاسبةالسعرات الحرارية",
+ "caloriesCalculatorDesc": "اكتشف عدد السعرات الحرارية التي تحتاجها يومياً للحفاظ على الوزن أو فقدانه.",
+ "bmrCalculator": "حاسبة معدل الأيض الأساسي",
+ "bmrCalculatorDesc": "حدد معدل الأيض الأساسي ومتطلباتك اليومية من الطاقة.",
+ "idealBodyWeight": "الوزن المثالي للجسم",
+ "idealBodyWeightDesc": "اكتشف وزنك المثالي بناءً على الطول والعمر وبنية الجسم.",
+ "bodyFatCalculator": "حاسبة دهون الجسم",
+ "bodyFatCalculatorDesc": "قدّر نسبة دهون جسمك لتتبع أهداف اللياقة والصحة.",
+ "carbsProteinFat": "الكربوهيدرات البروتين والدهون",
+ "carbsProteinFatDesc": "وازن بين العناصر الغذائية مع النسب الصحيحة من الكربوهيدرات والبروتين والدهون.",
+ "ovulationPeriod": "فترة الإباضة",
+ "ovulationPeriodDesc": "تتبع فترة خصوبتك وخطط للحمل بدقة.",
+ "deliveryDueDate": "تاريخ الولادة المتوقع",
+ "deliveryDueDateDesc": "احسب تاريخ ولادتك المتوقع وراقب مراحل الحمل.",
"low": "منخفض",
"preDiabetic": "ما قبل السكري",
"high": "مرتفع",
diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json
index ca64e0a9..c2f6320c 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -1168,14 +1168,22 @@
"monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.",
"triglyceridesFatBlood": "Triglycerides Fat Blood",
"understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.",
- "bmiCalculator": "BMI\nCalculator",
- "caloriesCalculator": "Calories\nCalculator",
- "bmrCalculator": "BMR\nCalculator",
- "idealBodyWeight": "Ideal Body\nWeight",
- "bodyFatCalculator": "Body Fat\nCalculator",
- "carbsProteinFat": "Carbs\nProtein & Fat",
- "ovulationPeriod": "Ovulation\nPeriod",
- "deliveryDueDate": "Delivery\nDue Date",
+ "bmiCalculator": "BMI Calculator",
+ "bmiCalculatorDesc": "Calculate your body mass index to understand your healthy weight range.",
+ "caloriesCalculator": "Calories Calculator",
+ "caloriesCalculatorDesc": "Find out how many calories you need daily to maintain or lose weight.",
+ "bmrCalculator": "BMR Calculator",
+ "bmrCalculatorDesc": "Determine your basal metabolic rate and daily energy requirements.",
+ "idealBodyWeight": "Ideal Body Weight",
+ "idealBodyWeightDesc": "Discover your ideal weight based on height, age, and body structure.",
+ "bodyFatCalculator": "Body Fat Calculator",
+ "bodyFatCalculatorDesc": "Estimate your body fat percentage to track fitness and health goals.",
+ "carbsProteinFat": "Carbs Protein & Fat",
+ "carbsProteinFatDesc": "Balance your macros with the right carbs, protein, and fat ratios.",
+ "ovulationPeriod": "Ovulation Period",
+ "ovulationPeriodDesc": "Track your fertile window and plan for conception with accuracy.",
+ "deliveryDueDate": "Delivery Due Date",
+ "deliveryDueDateDesc": "Calculate your expected delivery date and monitor pregnancy milestones.",
"low": "Low",
"preDiabetic": "Pre-diabetic",
"high": "High",
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 743505b9..aab9e25d 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -363,6 +363,14 @@ class AppAssets {
static const String files = '$svgBasePath/files.svg';
static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg';
+ //Health Calculators
+ static const String bodyfatcalc = '$svgBasePath/bfc.svg';
+ static const String duedatecalc = '$svgBasePath/ddc.svg';
+ static const String idealbodyweightcacl = '$svgBasePath/ibwc.svg';
+ static const String ovluationperiod = '$svgBasePath/op.svg';
+ static const String bmicalc = '$svgBasePath/bmi1.svg';
+ static const String bmrcalc = '$svgBasePath/bmr1.svg';
+
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart
index 6f043e26..dfb400be 100644
--- a/lib/core/utils/calender_utils_new.dart
+++ b/lib/core/utils/calender_utils_new.dart
@@ -52,13 +52,7 @@ class CalenderUtilsNew {
});
}
- FutureOr createOrUpdateEvent(
- {required String title,
- required String description,
- required String location,
- DateTime? scheduleDateTime,
- String? eventId,
- int? reminderMinutes}) async {
+ FutureOr createOrUpdateEvent({required String title, required String description, required String location, DateTime? scheduleDateTime, String? eventId, int? reminderMinutes}) async {
if (writableCalender.isEmpty) {
await getCalenders();
}
@@ -93,19 +87,19 @@ class CalenderUtilsNew {
// return eventId.isNotEmpty;
}
- FutureOr createMultipleEvents(
- {required int reminderMinutes,
- int? frequencyNumber,
- required int days,
- required String orderDate,
- required String itemDescriptionN,
- required String route,
- Function(String)? onFailure,
- String? prescriptionNumber,
- DateTime? scheduleDateTime,
- }) async {
+ FutureOr createMultipleEvents({
+ required int reminderMinutes,
+ int? frequencyNumber,
+ required int days,
+ required String orderDate,
+ required String itemDescriptionN,
+ required String route,
+ Function(String)? onFailure,
+ String? prescriptionNumber,
+ DateTime? scheduleDateTime,
+ }) async {
DateTime currentDay = DateTime.now();
- DateTime actualDate = scheduleDateTime??DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0);
+ DateTime actualDate = scheduleDateTime ?? DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0);
print("the frequency is $frequencyNumber");
frequencyNumber ??= 2; //Some time frequency number is null so by default will be 2
int interval = calculateIntervalAsPerFrequency(frequencyNumber);
diff --git a/lib/features/my_appointments/models/reminder_type.dart b/lib/features/my_appointments/models/reminder_type.dart
new file mode 100644
index 00000000..f13f20a0
--- /dev/null
+++ b/lib/features/my_appointments/models/reminder_type.dart
@@ -0,0 +1,50 @@
+/// Enum to define different types of reminders
+enum ReminderType {
+ /// Reminder for appointment schedule
+ appointment,
+
+ /// Reminder for payment due
+ payment,
+
+ /// Reminder for check-in
+ checkIn,
+}
+
+extension ReminderTypeExtension on ReminderType {
+ /// Get display name for the reminder type
+ String get displayName {
+ switch (this) {
+ case ReminderType.appointment:
+ return 'Appointment Reminder';
+ case ReminderType.payment:
+ return 'Payment Reminder';
+ case ReminderType.checkIn:
+ return 'Check-In Reminder';
+ }
+ }
+
+ /// Get description for the reminder type
+ String get description {
+ switch (this) {
+ case ReminderType.appointment:
+ return 'Appointment scheduled reminder';
+ case ReminderType.payment:
+ return 'Payment due reminder';
+ case ReminderType.checkIn:
+ return 'Check-in reminder';
+ }
+ }
+
+ /// Get event ID prefix for calendar events
+ String get eventIdPrefix {
+ switch (this) {
+ case ReminderType.appointment:
+ return 'APPT';
+ case ReminderType.payment:
+ return 'PAY';
+ case ReminderType.checkIn:
+ return 'CHKIN';
+ }
+ }
+}
+
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index 842c4a61..942a51e6 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -8,6 +8,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/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@@ -20,6 +21,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@@ -1113,9 +1115,13 @@ class MyAppointmentsViewModel extends ChangeNotifier {
/// Creates a reminder for the appointment
/// Shows a bottom sheet to select reminder time and creates calendar event
+ /// [reminderType] specifies the type of reminder (appointment, payment, or check-in)
+ /// For payment and check-in reminders, shows only 1, 2, 3 hours before appointment
+ /// Automatically removes previous reminders when creating a new one
Future createReminder({
required BuildContext context,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ ReminderType reminderType = ReminderType.appointment,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
@@ -1123,39 +1129,329 @@ class MyAppointmentsViewModel extends ChangeNotifier {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
DateTime endDate = DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate);
- BottomSheetUtils().showReminderBottomSheet(
- context,
- endDate,
- patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
- "${patientAppointmentHistoryResponseModel.appointmentNo}",
- "",
- "",
- title: "Appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}",
- description: "${patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${patientAppointmentHistoryResponseModel.appointmentDate}",
- onSuccess: () {
- setAppointmentReminder(true, patientAppointmentHistoryResponseModel);
- if (onSuccess != null) {
- onSuccess(true);
- }
- },
- isMultiAllowed: true,
- onMultiDateSuccess: (int selectedIndex) async {
- bool isEventAddedOrRemoved = await calender.createOrUpdateEvent(
- title: "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}",
- description: "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}",
- scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
- eventId: "${patientAppointmentHistoryResponseModel.appointmentNo}",
- location: '',
- reminderMinutes: selectedIndex,
- );
-
- setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel);
+ // Generate title and description based on reminder type
+ String title;
+ String description;
+ String eventIdPrefix = reminderType.eventIdPrefix;
+
+ switch (reminderType) {
+ case ReminderType.appointment:
+ title = "Appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
+ description = "${patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${patientAppointmentHistoryResponseModel.appointmentDate}";
+ break;
+ case ReminderType.payment:
+ title = "Payment Due for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ description = "Payment reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ case ReminderType.checkIn:
+ title = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ description = "Don't forget to check in for your appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
+ break;
+ }
- if (onSuccess != null) {
- onSuccess(isEventAddedOrRemoved);
- }
- },
+ // For payment and check-in, remove existing reminders first
+ if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) {
+ // Remove all existing reminder types
+ await _removeAllRemindersForAppointment(patientAppointmentHistoryResponseModel);
+ }
+
+ // For payment and check-in reminders, use custom hour-based options
+ if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) {
+ _showHourBasedReminderSheet(
+ context: context,
+ patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
+ title: title,
+ description: description,
+ eventIdPrefix: eventIdPrefix,
+ endDate: endDate,
+ onSuccess: onSuccess,
+ onError: onError,
+ );
+ } else {
+ // For appointment reminders, use the standard reminder sheet with all options
+ BottomSheetUtils().showReminderBottomSheet(
+ context,
+ endDate,
+ patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
+ "${patientAppointmentHistoryResponseModel.appointmentNo}",
+ "",
+ "",
+ title: title,
+ description: description,
+ onSuccess: () {
+ setAppointmentReminder(true, patientAppointmentHistoryResponseModel);
+ if (onSuccess != null) {
+ onSuccess(true);
+ }
+ },
+ isMultiAllowed: true,
+ onMultiDateSuccess: (int selectedIndex) async {
+ String eventTitle;
+ String eventDescription;
+
+ switch (reminderType) {
+ case ReminderType.appointment:
+ eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ case ReminderType.payment:
+ eventTitle = "Payment Due Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
+ eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ case ReminderType.checkIn:
+ eventTitle = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}";
+ eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ }
+
+ bool isEventAddedOrRemoved = await calender.createOrUpdateEvent(
+ title: eventTitle,
+ description: eventDescription,
+ scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
+ eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
+ location: patientAppointmentHistoryResponseModel.projectName ?? '',
+ reminderMinutes: selectedIndex,
+ );
+
+ setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel);
+
+ if (onSuccess != null) {
+ onSuccess(isEventAddedOrRemoved);
+ }
+ },
+ );
+ }
+ } catch (e) {
+ if (onError != null) {
+ onError(e.toString());
+ }
+ }
+ }
+
+ /// Removes all reminder types for an appointment (internal helper)
+ Future _removeAllRemindersForAppointment(PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel) async {
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
+
+ // Remove all possible reminder types
+ for (var type in ReminderType.values) {
+ try {
+ await calender.checkAndRemove(
+ id: "${type.eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
+ );
+ } catch (e) {
+ // Ignore errors if reminder doesn't exist
+ debugPrint("No existing ${type.displayName} to remove");
+ }
+ }
+ }
+
+ /// Shows hour-based reminder selection for payment and check-in reminders
+ void _showHourBasedReminderSheet({
+ required BuildContext context,
+ required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ required ReminderType reminderType,
+ required String title,
+ required String description,
+ required String eventIdPrefix,
+ required DateTime endDate,
+ Function(bool)? onSuccess,
+ Function(String)? onError,
+ }) {
+ showCommonBottomSheetWithoutHeight(
+ context,
+ title: "Select Reminder Time",
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Center(
+ child: Text(
+ "When would you like to be reminded?",
+ style: TextStyle(
+ fontSize: 14.f,
+ color: AppColors.greyTextColor,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ _buildHourOption(
+ context: context,
+ hours: 1,
+ icon: Icons.alarm,
+ patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
+ title: title,
+ description: description,
+ eventIdPrefix: eventIdPrefix,
+ endDate: endDate,
+ onSuccess: onSuccess,
+ onError: onError,
+ ),
+ SizedBox(height: 12.h),
+ _buildHourOption(
+ context: context,
+ hours: 2,
+ icon: Icons.alarm,
+ patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
+ title: title,
+ description: description,
+ eventIdPrefix: eventIdPrefix,
+ endDate: endDate,
+ onSuccess: onSuccess,
+ onError: onError,
+ ),
+ SizedBox(height: 12.h),
+ _buildHourOption(
+ context: context,
+ hours: 3,
+ icon: Icons.alarm,
+ patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
+ title: title,
+ description: description,
+ eventIdPrefix: eventIdPrefix,
+ endDate: endDate,
+ onSuccess: onSuccess,
+ onError: onError,
+ ),
+ ],
+ ),
+ ),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ }
+
+ /// Builds an hour option button for payment/check-in reminders
+ Widget _buildHourOption({
+ required BuildContext context,
+ required int hours,
+ required IconData icon,
+ required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ required ReminderType reminderType,
+ required String title,
+ required String description,
+ required String eventIdPrefix,
+ required DateTime endDate,
+ Function(bool)? onSuccess,
+ Function(String)? onError,
+ }) {
+ return GestureDetector(
+ onTap: () async {
+ Navigator.of(context).pop(); // Close the selection sheet
+
+ // Create reminder with selected hours (converted to minutes)
+ await _createReminderWithHours(
+ hours: hours,
+ patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
+ title: title,
+ description: description,
+ eventIdPrefix: eventIdPrefix,
+ endDate: endDate,
+ onSuccess: onSuccess,
+ onError: onError,
+ );
+ },
+ child: Container(
+ padding: EdgeInsets.all(16.h),
+ decoration: BoxDecoration(
+ color: AppColors.whiteColor,
+ border: Border.all(color: AppColors.borderOnlyColor),
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Row(
+ children: [
+ Container(
+ padding: EdgeInsets.all(12.h),
+ decoration: BoxDecoration(
+ color: AppColors.primaryRedColor.withAlpha(26),
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: Icon(
+ icon,
+ color: AppColors.primaryRedColor,
+ size: 24.h,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Text(
+ "$hours ${hours == 1 ? 'Hour' : 'Hours'} Before Appointment",
+ style: TextStyle(
+ fontSize: 16.f,
+ fontWeight: FontWeight.w600,
+ color: AppColors.textColor,
+ ),
+ ),
+ ),
+ Icon(
+ Icons.arrow_forward_ios,
+ size: 16.h,
+ color: AppColors.greyTextColor,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ /// Creates reminder with specific hours before appointment
+ Future _createReminderWithHours({
+ required int hours,
+ required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ required ReminderType reminderType,
+ required String title,
+ required String description,
+ required String eventIdPrefix,
+ required DateTime endDate,
+ Function(bool)? onSuccess,
+ Function(String)? onError,
+ }) async {
+ try {
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
+
+ // Convert hours to minutes
+ int reminderMinutes = hours * 60;
+
+ String eventTitle;
+ String eventDescription;
+
+ switch (reminderType) {
+ case ReminderType.appointment:
+ eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ case ReminderType.payment:
+ eventTitle = "Payment Due - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ case ReminderType.checkIn:
+ eventTitle = "Check-In Now - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}";
+ eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}";
+ break;
+ }
+
+ bool isEventAddedOrRemoved = await calender.createOrUpdateEvent(
+ title: eventTitle,
+ description: eventDescription,
+ scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
+ eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
+ location: patientAppointmentHistoryResponseModel.projectName ?? '',
+ reminderMinutes: reminderMinutes,
);
+
+ setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel);
+
+ if (onSuccess != null) {
+ onSuccess(isEventAddedOrRemoved);
+ }
} catch (e) {
if (onError != null) {
onError(e.toString());
@@ -1165,16 +1461,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
/// Deletes the reminder for the appointment
/// Removes the calendar event associated with the appointment
+ /// [reminderType] specifies the type of reminder to delete (appointment, payment, or check-in)
Future deleteReminder({
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ ReminderType reminderType = ReminderType.appointment,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
try {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
+ String eventIdPrefix = reminderType.eventIdPrefix;
bool isEventRemoved = await calender.checkAndRemove(
- id: "${patientAppointmentHistoryResponseModel.appointmentNo}",
+ id: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
);
// Update the reminder status (inverted because we're removing)
@@ -1192,10 +1491,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
/// Toggle reminder for an appointment
/// Creates or deletes reminder based on the current state
+ /// [reminderType] specifies the type of reminder (appointment, payment, or check-in)
Future toggleReminder({
required BuildContext context,
required bool shouldCreateReminder,
required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ required ReminderType reminderType ,
Function(bool)? onSuccess,
Function(String)? onError,
}) async {
@@ -1203,12 +1504,14 @@ class MyAppointmentsViewModel extends ChangeNotifier {
await createReminder(
context: context,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
onSuccess: onSuccess,
onError: onError,
);
} else {
await deleteReminder(
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
+ reminderType: reminderType,
onSuccess: onSuccess,
onError: onError,
);
diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart
index 7722c8d8..710b9d20 100644
--- a/lib/generated/codegen_loader.g.dart
+++ b/lib/generated/codegen_loader.g.dart
@@ -2893,14 +2893,14 @@ static const Map _en_US = {
"monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.",
"triglyceridesFatBlood": "Triglycerides Fat Blood",
"understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.",
- "bmiCalculator": "BMI\nCalculator",
- "caloriesCalculator": "Calories\nCalculator",
- "bmrCalculator": "BMR\nCalculator",
- "idealBodyWeight": "Ideal Body\nWeight",
- "bodyFatCalculator": "Body Fat\nCalculator",
- "carbsProteinFat": "Carbs\nProtein & Fat",
- "ovulationPeriod": "Ovulation\nPeriod",
- "deliveryDueDate": "Delivery\nDue Date",
+ "bmiCalculator": "BMI Calculator",
+ "caloriesCalculator": "Calories Calculator",
+ "bmrCalculator": "BMR Calculator",
+ "idealBodyWeight": "Ideal Body Weight",
+ "bodyFatCalculator": "Body Fat Calculator",
+ "carbsProteinFat": "Carbs Protein & Fat",
+ "ovulationPeriod": "Ovulation Period",
+ "deliveryDueDate": "Delivery Due Date",
"low": "Low",
"preDiabetic": "Pre-diabetic",
"high": "High",
diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart
index 64577cbb..10528bb3 100644
--- a/lib/generated/locale_keys.g.dart
+++ b/lib/generated/locale_keys.g.dart
@@ -1176,13 +1176,21 @@ abstract class LocaleKeys {
static const triglyceridesFatBlood = 'triglyceridesFatBlood';
static const understandTriglyceridesImpact = 'understandTriglyceridesImpact';
static const bmiCalculator = 'bmiCalculator';
+ static const bmiCalculatorDesc = 'bmiCalculatorDesc';
static const caloriesCalculator = 'caloriesCalculator';
+ static const caloriesCalculatorDesc = 'caloriesCalculatorDesc';
static const bmrCalculator = 'bmrCalculator';
+ static const bmrCalculatorDesc = 'bmrCalculatorDesc';
static const idealBodyWeight = 'idealBodyWeight';
+ static const idealBodyWeightDesc = 'idealBodyWeightDesc';
static const bodyFatCalculator = 'bodyFatCalculator';
+ static const bodyFatCalculatorDesc = 'bodyFatCalculatorDesc';
static const carbsProteinFat = 'carbsProteinFat';
+ static const carbsProteinFatDesc = 'carbsProteinFatDesc';
static const ovulationPeriod = 'ovulationPeriod';
+ static const ovulationPeriodDesc = 'ovulationPeriodDesc';
static const deliveryDueDate = 'deliveryDueDate';
+ static const deliveryDueDateDesc = 'deliveryDueDateDesc';
static const low = 'low';
static const preDiabetic = 'preDiabetic';
static const high = 'high';
diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart
index 29768c77..a0fdb2c9 100644
--- a/lib/presentation/appointments/appointment_details_page.dart
+++ b/lib/presentation/appointments/appointment_details_page.dart
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
+import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
@@ -32,13 +34,11 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_rating_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
-import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
+import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
-import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
-import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@@ -95,18 +95,54 @@ class _AppointmentDetailsPageState extends State {
} else {
isAppointmentWithin4Hours = true;
}
+
+ // Check if reminder exists in calendar and update the flag
+ await _checkAndUpdateReminderStatus();
}
});
// Initialize countdown timer for payment
- if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 ||
- widget.patientAppointmentHistoryResponseModel.nextAction == 20) {
+ if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) {
_startCountdownTimer();
}
super.initState();
}
+ /// Checks if a reminder exists in the calendar for this appointment
+ /// and updates the hasReminder flag accordingly
+ Future _checkAndUpdateReminderStatus() async {
+ try {
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
+
+ // Check if reminder exists for this appointment
+ bool reminderExists = await calender.checkIfEventExist(
+ widget.patientAppointmentHistoryResponseModel.appointmentNo.toString() ?? "",
+ );
+
+ // Update the flag if it's different from current state
+ if (mounted && widget.patientAppointmentHistoryResponseModel.hasReminder != reminderExists) {
+ setState(() {
+ widget.patientAppointmentHistoryResponseModel.hasReminder = reminderExists;
+ });
+
+ // Also update in view model to keep data consistent
+ myAppointmentsViewModel.setAppointmentReminder(reminderExists, widget.patientAppointmentHistoryResponseModel);
+ }
+
+ debugPrint("Reminder status for appointment ${widget.patientAppointmentHistoryResponseModel.appointmentNo}: $reminderExists");
+ setState(() {});
+ } catch (e) {
+ debugPrint("Error checking reminder status: $e");
+ // If there's an error, default to false
+ if (mounted) {
+ setState(() {
+ widget.patientAppointmentHistoryResponseModel.hasReminder = false;
+ });
+ }
+ }
+ }
+
void _startCountdownTimer() {
final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate);
_updateTimeRemaining();
@@ -327,7 +363,58 @@ class _AppointmentDetailsPageState extends State {
onRescheduleTap: () async {
openDoctorScheduleCalendar();
},
+ timerWidget: Column(
+ children: [
+ SizedBox(
+ height: 16.w,
+ ),
+ Divider(
+ height: 1,
+ color: AppColors.dividerColor,
+ ),
+ SizedBox(
+ height: 16.w,
+ ),
+ Directionality(
+ textDirection: ui.TextDirection.ltr,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
+ LocaleKeys.days.tr(context: context),
+ ),
+ ),
+ _buildTimeSeparator(),
+ Expanded(
+ child: _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
+ LocaleKeys.hours.tr(context: context),
+ ),
+ ),
+ _buildTimeSeparator(),
+ Expanded(
+ child: _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
+ LocaleKeys.minutes.tr(context: context),
+ ),
+ ),
+ _buildTimeSeparator(),
+ Expanded(
+ child: _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
+ LocaleKeys.seconds.tr(context: context),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
),
+
SizedBox(height: 16.h),
!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? Column(
@@ -514,47 +601,118 @@ class _AppointmentDetailsPageState extends State {
],
),
const Spacer(),
- Switch(
- activeThumbColor: AppColors.successColor,
- // activeTrackColor: AppColors.successColor.withValues(alpha: .15),
- value: widget.patientAppointmentHistoryResponseModel.hasReminder!,
+ BellAnimatedSwitch(
+ initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false,
+ activeColor: AppColors.successColor.withOpacity(0.2),
+ inactiveColor: AppColors.lightGrayBGColor,
+ activeCircleColor: AppColors.successColor,
+ inactiveCircleColor: AppColors.greyTextColor,
+ activeIconColor: AppColors.bgGreenColor,
+ inactiveIconColor: AppColors.greyTextColor,
+ activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h),
+ inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h),
onChanged: (newValue) async {
- await myAppointmentsViewModel.toggleReminder(
- context: context,
- shouldCreateReminder: newValue,
- patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
- onSuccess: (result) {
- showCommonBottomSheet(
- context,
- child: Utils.getSuccessWidget(
- loadingText: newValue ? LocaleKeys.reminderSuccess.tr(context: context) : LocaleKeys.reminderCancelSuccess.tr(context: context),
- ),
- callBackFunc: (str) {},
- title: "",
- isCloseButtonVisible: false,
- isDismissible: true,
- isFullScreen: false,
- isAutoDismiss: true,
- height: ResponsiveExtension.screenHeight * 0.3,
- );
- },
- onError: (error) {
- setState(() {
- // Revert the switch if there was an error
- widget.patientAppointmentHistoryResponseModel.hasReminder = !newValue;
- });
- showCommonBottomSheetWithoutHeight(
- context,
- title: LocaleKeys.notice.tr(context: context),
- child: Utils.getErrorWidget(loadingText: error),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- },
- );
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
+ bool isEventAddedOrRemoved = false;
+ if (newValue == true) {
+ DateTime startDate = DateTime.now();
+ DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate);
+ BottomSheetUtils().showReminderBottomSheet(
+ context,
+ endDate,
+ widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
+ "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "",
+ "",
+ "",
+ title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
+ description:
+ "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
+ onSuccess: () {
+ setState(() {
+ myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
+ });
+ },
+ isMultiAllowed: true,
+ onMultiDateSuccess: (int selectedIndex) async {
+ isEventAddedOrRemoved = await calender.createOrUpdateEvent(
+ title:
+ "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
+ description:
+ "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
+ scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate),
+ eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
+ location: '',
+ reminderMinutes: selectedIndex);
+ setState(() {
+ myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
+ });
+ },
+ );
+ } else {
+ isEventAddedOrRemoved = await calender.checkAndRemove(
+ id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
+ );
+ setState(() {
+ myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
+ });
+ }
},
- ),
+ )
+
+ // Switch(
+ // activeThumbColor: AppColors.successColor,
+ // // activeTrackColor: AppColors.successColor.withValues(alpha: .15),
+ // value: widget.patientAppointmentHistoryResponseModel.hasReminder!,
+ // onChanged: (newValue) async {
+ // CalenderUtilsNew calender = CalenderUtilsNew.instance;
+ // bool isEventAddedOrRemoved = false;
+ // if(newValue == true){
+ // DateTime startDate = DateTime.now();
+ // DateTime endDate = DateUtil.convertStringToDate(widget
+ // .patientAppointmentHistoryResponseModel.appointmentDate);
+ // BottomSheetUtils().showReminderBottomSheet(
+ // context,
+ // endDate,
+ // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"",
+ // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"",
+ // "",
+ // "",
+ // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
+ // description:
+ // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
+ // onSuccess: () {
+ // setState(() {
+ // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
+ // });
+ // },
+ // isMultiAllowed: true,
+ // onMultiDateSuccess: (int selectedIndex) async {
+ // isEventAddedOrRemoved = await calender.createOrUpdateEvent(
+ // title:
+ // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
+ // description:
+ // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
+ // scheduleDateTime: DateUtil.convertStringToDate(widget
+ // .patientAppointmentHistoryResponseModel.appointmentDate),
+ // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
+ // location: '',
+ // reminderMinutes: selectedIndex
+ // );
+ // setState(() {
+ // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
+ // });
+ // },
+ // );
+ // }else {
+ // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
+ // setState(() {
+ // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
+ // });
+ // }
+ //
+ //
+ // },
+ // ),
],
).paddingSymmetrical(16.h, 16.h),
),
@@ -1234,39 +1392,42 @@ class _AppointmentDetailsPageState extends State {
children: [
// Message text
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(
- color: AppColors.textColor,
- isCenter: true,
- ),
- SizedBox(height: 24.h),
- // Countdown Timer - DD : HH : MM : SS format with labels
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // Days
- _buildTimeUnit(
- _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
- LocaleKeys.days.tr(context: context),
- ),
- _buildTimeSeparator(),
- // Hours
- _buildTimeUnit(
- _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
- LocaleKeys.hours.tr(context: context),
- ),
- _buildTimeSeparator(),
- // Minutes
- _buildTimeUnit(
- _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
- LocaleKeys.minutes.tr(context: context),
- ),
- _buildTimeSeparator(),
- // Seconds
- _buildTimeUnit(
- _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
- LocaleKeys.seconds.tr(context: context),
+ color: AppColors.textColor,
+ isCenter: true,
),
- ],
+ SizedBox(height: 24.h),
+ // Countdown Timer - DD : HH : MM : SS format with labels - Always LTR
+ Directionality(
+ textDirection: ui.TextDirection.ltr,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Days
+ _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
+ LocaleKeys.days.tr(context: context),
+ ),
+ _buildTimeSeparator(),
+ // Hours
+ _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
+ LocaleKeys.hours.tr(context: context),
+ ),
+ _buildTimeSeparator(),
+ // Minutes
+ _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
+ LocaleKeys.minutes.tr(context: context),
+ ),
+ _buildTimeSeparator(),
+ // Seconds
+ _buildTimeUnit(
+ _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
+ LocaleKeys.seconds.tr(context: context),
+ ),
+ ],
+ ),
),
SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon
diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
index 4b5df07b..cfc0420a 100644
--- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart
+++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
@@ -24,7 +24,13 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class AppointmentDoctorCard extends StatelessWidget {
const AppointmentDoctorCard(
- {super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap, this.renderWidgetForERDisplay = false});
+ {super.key,
+ required this.patientAppointmentHistoryResponseModel,
+ required this.onRescheduleTap,
+ required this.onCancelTap,
+ required this.onAskDoctorTap,
+ this.renderWidgetForERDisplay = false,
+ this.timerWidget});
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
final VoidCallback onRescheduleTap;
@@ -32,6 +38,8 @@ class AppointmentDoctorCard extends StatelessWidget {
final VoidCallback onAskDoctorTap;
final bool renderWidgetForERDisplay;
+ final Widget? timerWidget;
+
@override
Widget build(BuildContext context) {
return Container(
@@ -146,39 +154,39 @@ class AppointmentDoctorCard extends StatelessWidget {
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
- selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
- selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
- selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
- selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
- selectedDoctor.nationalityFlagURL = "";
- selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
- selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
- selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
- selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
- selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
+ selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
+ selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
+ selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
+ selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
+ selectedDoctor.nationalityFlagURL = "";
+ selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
+ selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
+ selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
+ selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
+ selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
- getIt.get().setSelectedDoctor(selectedDoctor);
- LoaderBottomSheet.showLoader();
- await getIt.get().getDoctorProfile(onSuccess: (dynamic respData) {
- LoaderBottomSheet.hideLoader();
- Navigator.of(context).push(
- CustomPageRoute(
- page: DoctorProfilePage(
- isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
- ),
- ),
- );
- }, onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- })
+ getIt.get().setSelectedDoctor(selectedDoctor);
+ LoaderBottomSheet.showLoader();
+ await getIt.get().getDoctorProfile(onSuccess: (dynamic respData) {
+ LoaderBottomSheet.hideLoader();
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: DoctorProfilePage(
+ isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
+ ),
+ ),
+ );
+ }, onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ })
],
),
SizedBox(height: 8.h),
@@ -188,6 +196,8 @@ class AppointmentDoctorCard extends StatelessWidget {
AppointmentType.isArrived(patientAppointmentHistoryResponseModel),
),
),
+ if (timerWidget != null)
+ timerWidget ?? SizedBox()
],
),
),
@@ -215,26 +225,26 @@ class AppointmentDoctorCard extends StatelessWidget {
iconColor: AppColors.primaryRedColor,
)
:
- // !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
- // ? CustomButton(
- // text: LocaleKeys.rebookSameDoctor.tr(),
- // onPressed: () {
- // onRescheduleTap();
- // },
- // backgroundColor: AppColors.greyColor,
- // borderColor: AppColors.greyColor,
- // textColor: AppColors.blackColor,
- // fontSize: 12.f,
- // isBold: true,
- // borderRadius: 12.r,
- // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
- // height: 40.h,
- // icon: AppAssets.rebook_appointment_icon,
- // iconColor: AppColors.blackColor,
- // iconSize: 14.h,
- // )
- // :
- SizedBox.shrink();
+ // !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
+ // ? CustomButton(
+ // text: LocaleKeys.rebookSameDoctor.tr(),
+ // onPressed: () {
+ // onRescheduleTap();
+ // },
+ // backgroundColor: AppColors.greyColor,
+ // borderColor: AppColors.greyColor,
+ // textColor: AppColors.blackColor,
+ // fontSize: 12.f,
+ // isBold: true,
+ // borderRadius: 12.r,
+ // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
+ // height: 40.h,
+ // icon: AppAssets.rebook_appointment_icon,
+ // iconColor: AppColors.blackColor,
+ // iconSize: 14.h,
+ // )
+ // :
+ SizedBox.shrink();
} else {
return patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? CustomButton(
diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart
index 6a1838f9..86702dbb 100644
--- a/lib/presentation/book_appointment/review_appointment_page.dart
+++ b/lib/presentation/book_appointment/review_appointment_page.dart
@@ -321,8 +321,8 @@ class _ReviewAppointmentPageState extends State {
void initiateBookAppointment() async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
- // LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
- LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
+ LoaderBottomSheet.showLoader(
+ loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
if (bookAppointmentsViewModel.isLiveCareSchedule) {
@@ -365,47 +365,10 @@ class _ReviewAppointmentPageState extends State {
}, onSuccess: (apiResp) async {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
- showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () async {
+ showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
- bool newValue = true;
- await myAppointmentsViewModel.toggleReminder(
- context: context,
- shouldCreateReminder: newValue,
- patientAppointmentHistoryResponseModel: apiResp.patientAppointmentHistoryResponseModel,
- onSuccess: (result) {
- showCommonBottomSheet(
- context,
- child: Utils.getSuccessWidget(
- loadingText: newValue ? LocaleKeys.reminderSuccess.tr(context: context) : LocaleKeys.reminderCancelSuccess.tr(context: context),
- ),
- callBackFunc: (str) {
- Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
- },
- title: "",
- isCloseButtonVisible: false,
- isDismissible: true,
- isFullScreen: false,
- isAutoDismiss: true,
- height: ResponsiveExtension.screenHeight * 0.3,
- );
- },
- onError: (error) {
- setState(() {
- apiResp.patientAppointmentHistoryResponseModel.hasReminder = !newValue;
- });
- showCommonBottomSheetWithoutHeight(
- context,
- title: LocaleKeys.notice.tr(context: context),
- child: Utils.getErrorWidget(loadingText: error),
- callBackFunc: () {
- Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
- },
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- },
- );
+ Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
}, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true);
});
});
diff --git a/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart b/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart
new file mode 100644
index 00000000..ec3afc40
--- /dev/null
+++ b/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart
@@ -0,0 +1,441 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
+
+enum ReminderBottomSheetStep {
+ success,
+ reminderConfirmation,
+ reminderTypeSelection,
+ creatingReminder,
+ reminderSuccess,
+ reminderError,
+}
+
+class AppointmentSuccessReminderBottomSheet extends StatefulWidget {
+ final PatientAppointmentHistoryResponseModel appointmentModel;
+ final MyAppointmentsViewModel myAppointmentsViewModel;
+ final VoidCallback onComplete;
+
+ const AppointmentSuccessReminderBottomSheet({
+ Key? key,
+ required this.appointmentModel,
+ required this.myAppointmentsViewModel,
+ required this.onComplete,
+ }) : super(key: key);
+
+ @override
+ State createState() => _AppointmentSuccessReminderBottomSheetState();
+}
+
+class _AppointmentSuccessReminderBottomSheetState extends State {
+ ReminderBottomSheetStep currentStep = ReminderBottomSheetStep.success;
+ ReminderType? selectedReminderType;
+ String errorMessage = '';
+ bool isProcessing = false;
+
+ @override
+ void initState() {
+ super.initState();
+ // Auto-progress to reminder confirmation after showing success
+ Future.delayed(Duration(milliseconds: 2000), () {
+ if (mounted) {
+ setState(() {
+ currentStep = ReminderBottomSheetStep.reminderConfirmation;
+ });
+ }
+ });
+ }
+
+ void _handleNoReminder() {
+ Navigator.of(context).pop();
+ widget.onComplete();
+ }
+
+ void _handleYesReminder() {
+ setState(() {
+ currentStep = ReminderBottomSheetStep.reminderTypeSelection;
+ });
+ }
+
+ void _selectReminderType(ReminderType type) {
+ setState(() {
+ selectedReminderType = type;
+ });
+ }
+
+ Future _createReminder() async {
+ if (selectedReminderType == null) return;
+
+ setState(() {
+ currentStep = ReminderBottomSheetStep.creatingReminder;
+ isProcessing = true;
+ });
+
+ await widget.myAppointmentsViewModel.toggleReminder(
+ context: context,
+ shouldCreateReminder: true,
+ reminderType: selectedReminderType!,
+ patientAppointmentHistoryResponseModel: widget.appointmentModel,
+ onSuccess: (result) {
+ if (mounted) {
+ setState(() {
+ currentStep = ReminderBottomSheetStep.reminderSuccess;
+ isProcessing = false;
+ });
+ // Auto-close after showing success
+ Future.delayed(Duration(milliseconds: 2000), () {
+ if (mounted) {
+ Navigator.of(context).pop();
+ widget.onComplete();
+ }
+ });
+ }
+ },
+ onError: (error) {
+ if (mounted) {
+ setState(() {
+ currentStep = ReminderBottomSheetStep.reminderError;
+ errorMessage = error;
+ isProcessing = false;
+ });
+ }
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)),
+ ),
+ padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
+ child: AnimatedSwitcher(
+ duration: Duration(milliseconds: 300),
+ child: _buildStepContent(),
+ ),
+ );
+ }
+
+ Widget _buildStepContent() {
+ switch (currentStep) {
+ case ReminderBottomSheetStep.success:
+ return _buildSuccessStep();
+ case ReminderBottomSheetStep.reminderConfirmation:
+ return _buildReminderConfirmationStep();
+ case ReminderBottomSheetStep.reminderTypeSelection:
+ return _buildReminderTypeSelectionStep();
+ case ReminderBottomSheetStep.creatingReminder:
+ return _buildCreatingReminderStep();
+ case ReminderBottomSheetStep.reminderSuccess:
+ return _buildReminderSuccessStep();
+ case ReminderBottomSheetStep.reminderError:
+ return _buildReminderErrorStep();
+ }
+ }
+
+ Widget _buildSuccessStep() {
+ return Column(
+ key: ValueKey('success'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr(context: context)),
+ SizedBox(height: 16.h),
+ Center(
+ child: "Your appointment has been booked successfully!".toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildReminderConfirmationStep() {
+ return Column(
+ key: ValueKey('confirmation'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.notifications_active_outlined,
+ size: 56.h,
+ color: AppColors.primaryRedColor,
+ ),
+ SizedBox(height: 16.h),
+ Center(child: "Add Reminder?".toText20(isBold: true)),
+ SizedBox(height: 12.h),
+ Center(
+ child: "Would you like to add a reminder for this appointment?".toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ Row(
+ children: [
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.cancel.tr(context: context),
+ onPressed: _handleNoReminder,
+ backgroundColor: AppColors.whiteColor,
+ borderColor: AppColors.greyTextColor,
+ textColor: AppColors.greyTextColor,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 48.h,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.confirm.tr(context: context),
+ onPressed: _handleYesReminder,
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedColor,
+ textColor: Colors.white,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 48.h,
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+
+ Widget _buildReminderTypeSelectionStep() {
+ return Column(
+ key: ValueKey('type_selection'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Center(child: "Select Reminder Type".toText20(isBold: true)),
+ SizedBox(height: 8.h),
+ Center(
+ child: "Choose what type of reminder you want to set".toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ _buildReminderTypeCard(
+ type: ReminderType.appointment,
+ icon: Icons.event_outlined,
+ title: "Appointment Reminder",
+ description: "Get reminded about your upcoming appointment",
+ ),
+ SizedBox(height: 12.h),
+ _buildReminderTypeCard(
+ type: ReminderType.payment,
+ icon: Icons.payment_outlined,
+ title: "Payment Reminder",
+ description: "Get reminded about payment for this appointment",
+ ),
+ SizedBox(height: 12.h),
+ _buildReminderTypeCard(
+ type: ReminderType.checkIn,
+ icon: Icons.check_circle_outline,
+ title: "Check-In Reminder",
+ description: "Get reminded to check in for your appointment",
+ ),
+ SizedBox(height: 24.h),
+ Row(
+ children: [
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.cancel.tr(context: context),
+ onPressed: () {
+ setState(() {
+ currentStep = ReminderBottomSheetStep.reminderConfirmation;
+ selectedReminderType = null;
+ });
+ },
+ backgroundColor: AppColors.whiteColor,
+ borderColor: AppColors.greyTextColor,
+ textColor: AppColors.greyTextColor,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 48.h,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: CustomButton(
+ text: "Continue",
+ onPressed: selectedReminderType != null ? _createReminder : null,
+ backgroundColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76),
+ borderColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76),
+ textColor: Colors.white,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 48.h,
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+
+ Widget _buildReminderTypeCard({
+ required ReminderType type,
+ required IconData icon,
+ required String title,
+ required String description,
+ }) {
+ final isSelected = selectedReminderType == type;
+ return GestureDetector(
+ onTap: () => _selectReminderType(type),
+ child: Container(
+ padding: EdgeInsets.all(16.h),
+ decoration: BoxDecoration(
+ color: isSelected ? AppColors.primaryRedColor.withAlpha(26) : AppColors.scaffoldBgColor,
+ border: Border.all(
+ color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor,
+ width: isSelected ? 2 : 1,
+ ),
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Row(
+ children: [
+ Container(
+ padding: EdgeInsets.all(12.h),
+ decoration: BoxDecoration(
+ color: isSelected ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(26),
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: Icon(
+ icon,
+ color: isSelected ? Colors.white : AppColors.greyTextColor,
+ size: 24.h,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ title.toText16(
+ isBold: true,
+ color: isSelected ? AppColors.primaryRedColor : AppColors.textColor,
+ ),
+ SizedBox(height: 4.h),
+ description.toText12(
+ color: AppColors.greyTextColor,
+ ),
+ ],
+ ),
+ ),
+ if (isSelected)
+ Icon(
+ Icons.check_circle,
+ color: AppColors.primaryRedColor,
+ size: 24.h,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildCreatingReminderStep() {
+ return Column(
+ key: ValueKey('creating'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircularProgressIndicator(color: AppColors.primaryRedColor),
+ SizedBox(height: 24.h),
+ Center(
+ child: "Creating your reminder...".toText16(
+ isBold: true,
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Center(
+ child: "Please wait while we set up your reminder".toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildReminderSuccessStep() {
+ return Column(
+ key: ValueKey('reminder_success'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.check_circle,
+ size: 64.h,
+ color: AppColors.successColor,
+ ),
+ SizedBox(height: 16.h),
+ Center(
+ child: LocaleKeys.reminderSuccess.tr(context: context).toText20(
+ isBold: true,
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Center(
+ child: "Your ${selectedReminderType?.displayName ?? 'reminder'} has been set successfully!".toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildReminderErrorStep() {
+ return Column(
+ key: ValueKey('error'),
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.error_outline,
+ size: 64.h,
+ color: AppColors.primaryRedColor,
+ ),
+ SizedBox(height: 16.h),
+ Center(
+ child: "Error".toText20(
+ isBold: true,
+ color: AppColors.primaryRedColor,
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Center(
+ child: errorMessage.toText14(
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ CustomButton(
+ text: "Close",
+ onPressed: () {
+ Navigator.of(context).pop();
+ widget.onComplete();
+ },
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedColor,
+ textColor: Colors.white,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 48.h,
+ ),
+ ],
+ );
+ }
+}
+
diff --git a/lib/presentation/book_appointment/widgets/bell_switch.dart b/lib/presentation/book_appointment/widgets/bell_switch.dart
new file mode 100644
index 00000000..498ea6aa
--- /dev/null
+++ b/lib/presentation/book_appointment/widgets/bell_switch.dart
@@ -0,0 +1,178 @@
+import 'package:flutter/material.dart';
+
+class BellAnimatedSwitch extends StatefulWidget {
+ final bool initialValue;
+ final ValueChanged? onChanged;
+ final Color? activeColor;
+ final Color? inactiveColor;
+ final Color? activeIconColor;
+ final Color? inactiveIconColor;
+ final Color? activeCircleColor;
+ final Color? inactiveCircleColor;
+ final Widget? activeIcon;
+ final Widget? inactiveIcon;
+ final double width;
+ final double height;
+ final Duration animationDuration;
+
+ const BellAnimatedSwitch({
+ super.key,
+ this.initialValue = false,
+ this.onChanged,
+ this.activeColor,
+ this.inactiveColor,
+ this.activeIconColor,
+ this.inactiveIconColor,
+ this.activeCircleColor,
+ this.inactiveCircleColor,
+ this.activeIcon,
+ this.inactiveIcon,
+ this.width = 70,
+ this.height = 40,
+ this.animationDuration = const Duration(milliseconds: 300),
+ });
+
+ @override
+ State createState() => _BellAnimatedSwitchState();
+}
+
+class _BellAnimatedSwitchState extends State with SingleTickerProviderStateMixin {
+ late bool _isActive;
+ late AnimationController _animationController;
+ late Animation _circleAlignment;
+ late Animation _scaleAnimation;
+
+ @override
+ void initState() {
+ super.initState();
+ _isActive = widget.initialValue;
+ _animationController = AnimationController(
+ duration: widget.animationDuration,
+ vsync: this,
+ );
+
+ // Animation for the circle position (left/right)
+ _circleAlignment = AlignmentTween(
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ ).animate(
+ CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
+ );
+
+ // Scale animation for tap feedback
+ _scaleAnimation = Tween(begin: 1.0, end: 0.9).animate(
+ CurvedAnimation(
+ parent: _animationController,
+ curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
+ ),
+ );
+
+ if (_isActive) {
+ _animationController.value = 1.0;
+ }
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ void _toggle() {
+ setState(() {
+ _isActive = !_isActive;
+ if (_isActive) {
+ _animationController.forward();
+ } else {
+ _animationController.reverse();
+ }
+ widget.onChanged?.call(_isActive);
+ });
+ }
+
+ Color _getBackgroundColor() {
+ if (_isActive) {
+ return widget.activeColor ?? Colors.green.shade400;
+ }
+ return widget.inactiveColor ?? Colors.grey.shade300;
+ }
+
+ Color _getCircleColor() {
+ if (_isActive) {
+ return widget.activeCircleColor ?? Colors.white;
+ }
+ return widget.inactiveCircleColor ?? Colors.white;
+ }
+
+ Widget _getIcon() {
+ if (_isActive) {
+ return widget.activeIcon ?? Icon(
+ Icons.check,
+ size: 18,
+ color: widget.activeIconColor ?? Colors.green.shade400,
+ );
+ }
+ return widget.inactiveIcon ?? Icon(
+ Icons.close,
+ size: 18,
+ color: widget.inactiveIconColor ?? Colors.grey.shade600,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: _toggle,
+ child: AnimatedBuilder(
+ animation: _animationController,
+ builder: (context, child) {
+ return Transform.scale(
+ scale: _scaleAnimation.value,
+ child: Container(
+ width: widget.width,
+ height: widget.height,
+ decoration: BoxDecoration(
+ color: _getBackgroundColor(),
+ borderRadius: BorderRadius.circular(widget.height / 2),
+ // boxShadow: [
+ // BoxShadow(
+ // color: Colors.black.withAlpha(26),
+ // blurRadius: 4,
+ // offset: const Offset(0, 2),
+ // ),
+ // ],
+ ),
+ child: Stack(
+ children: [
+ // Animated circle with icon inside
+ Align(
+ alignment: _circleAlignment.value,
+ child: Container(
+ margin: const EdgeInsets.all(4),
+ width: widget.height - 8,
+ height: widget.height - 8,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: _getCircleColor(),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withAlpha(51),
+ blurRadius: 2,
+ offset: const Offset(0, 1),
+ ),
+ ],
+ ),
+ child: Center(
+ child: _getIcon(),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart
index bba6f705..877a5d9b 100644
--- a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart
+++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart
@@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
+import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class HealthCalculatorsPage extends StatefulWidget {
@@ -37,73 +38,89 @@ class _HealthCalculatorsPageState extends State {
return CollapsingListView(
title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(),
child: widget.type == HealthCalConEnum.calculator
- ? Column(
- children: [
- Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r),
- child: Row(
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
- SizedBox(width: 12.w),
- Flexible(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LocaleKeys.generalHealth.tr().toText16(isBold: true),
- LocaleKeys.relatedToBMICalories.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))
- ],
- ),
- ),
- SizedBox(
- width: 12.w,
- ),
- Transform.flip(
- flipX: getIt.get().isArabic(),
- child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
- ],
- ).paddingAll(16.w))
- .onPress(() {
- dialogService.showFamilyBottomSheetWithoutHWithChild(
- label: LocaleKeys.selectCalculator.tr(),
- message: "",
- child: showCalculatorsItems(type: HealthCalculatorEnum.general),
- onOkPressed: () {},
- );
- }),
- SizedBox(height: 16.h),
- Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r),
- child: Row(
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
- SizedBox(width: 12.w),
- Flexible(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LocaleKeys.womensHealth.tr().toText16(isBold: true),
- LocaleKeys.relatedToPeriodsOvulation.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))
- ],
- ),
- ),
- SizedBox(width: 12.w),
- Transform.flip(
- flipX: getIt.get().isArabic(),
- child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
- ],
- ).paddingAll(16.w))
- .onPress(() {
- dialogService.showFamilyBottomSheetWithoutHWithChild(
- label: LocaleKeys.selectCalculator.tr(),
- message: "",
- child: showCalculatorsItems(type: HealthCalculatorEnum.women),
- onOkPressed: () {},
- );
- }),
+ ? CustomExpandableList(
+ expansionMode: ExpansionMode.exactlyOne,
+ dividerColor: AppColors.dividerColor,
+ itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
+ items: [
+ ExpandableListItem(
+ title: LocaleKeys.womensHealth.tr(context: context).toText18(isBold: true),
+ expandedBackgroundColor: Colors.transparent,
+ initiallyExpanded: true,
+ children: [showCalculatorsItems(type: HealthCalculatorEnum.women)],
+ ),
+ ExpandableListItem(
+ title: LocaleKeys.generalHealth.tr(context: context).toText18(isBold: true),
+ expandedBackgroundColor: Colors.transparent,
+ initiallyExpanded: true,
+ children: [showCalculatorsItems(type: HealthCalculatorEnum.general)],
+ ),
],
- ).paddingSymmetrical(20.w, 24.h)
+ theme: ExpandableListTheme.custom(
+ defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor),
+ ),
+ ).paddingSymmetrical(16.w, 0.0)
+ // ? Column(
+ // children: [
+ // Container(
+ // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r),
+ // child: Row(
+ // children: [
+ // Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
+ // SizedBox(width: 12.w),
+ // Flexible(
+ // child: Column(
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // mainAxisAlignment: MainAxisAlignment.center,
+ // children: [LocaleKeys.generalHealth.tr().toText16(isBold: true), LocaleKeys.relatedToBMICalories.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))],
+ // ),
+ // ),
+ // SizedBox(
+ // width: 12.w,
+ // ),
+ // Transform.flip(
+ // flipX: getIt.get().isArabic(),
+ // child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
+ // ],
+ // ).paddingAll(16.w))
+ // .onPress(() {
+ // dialogService.showFamilyBottomSheetWithoutHWithChild(
+ // label: LocaleKeys.selectCalculator.tr(),
+ // message: "",
+ // child: showCalculatorsItems(type: HealthCalculatorEnum.general),
+ // onOkPressed: () {},
+ // );
+ // }),
+ // SizedBox(height: 16.h),
+ // Container(
+ // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r),
+ // child: Row(
+ // children: [
+ // Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
+ // SizedBox(width: 12.w),
+ // Flexible(
+ // child: Column(
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // mainAxisAlignment: MainAxisAlignment.center,
+ // children: [LocaleKeys.womensHealth.tr().toText16(isBold: true), LocaleKeys.relatedToPeriodsOvulation.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))],
+ // ),
+ // ),
+ // SizedBox(width: 12.w),
+ // Transform.flip(
+ // flipX: getIt.get().isArabic(),
+ // child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
+ // ],
+ // ).paddingAll(16.w))
+ // .onPress(() {
+ // dialogService.showFamilyBottomSheetWithoutHWithChild(
+ // label: LocaleKeys.selectCalculator.tr(),
+ // message: "",
+ // child: showCalculatorsItems(type: HealthCalculatorEnum.women),
+ // onOkPressed: () {},
+ // );
+ // }),
+ // ],
+ // ).paddingSymmetrical(20.w, 24.h)
: Column(
children: [
Container(
@@ -151,11 +168,7 @@ class _HealthCalculatorsPageState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LocaleKeys.bloodCholesterol.tr().toText16(isBold: true),
- LocaleKeys.monitorCholesterolLevels.tr()
- .toText12(isBold: true, color: Color(0xFF8F9AA3))
- ],
+ children: [LocaleKeys.bloodCholesterol.tr().toText16(isBold: true), LocaleKeys.monitorCholesterolLevels.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))],
),
),
SizedBox(width: 12.w),
@@ -183,8 +196,7 @@ class _HealthCalculatorsPageState extends State {
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.triglyceridesFatBlood.tr().toText16(isBold: true),
- LocaleKeys.understandTriglyceridesImpact.tr()
- .toText12(isBold: true, color: Color(0xFF8F9AA3))
+ LocaleKeys.understandTriglyceridesImpact.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))
],
),
),
@@ -204,49 +216,104 @@ class _HealthCalculatorsPageState extends State {
}
Widget showCalculatorsItems({required HealthCalculatorEnum type}) {
- return GridView.builder(
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3, // 4 icons per row
- crossAxisSpacing: 16.w,
- mainAxisSpacing: 16.w,
- childAspectRatio: 0.85),
- physics: NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length,
- padding: EdgeInsets.zero,
- itemBuilder: (BuildContext context, int index) {
- return HealthCard(
- icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon,
- labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title,
- onTap: () {
- Navigator.pop(context);
- Navigator.of(context).push(
- CustomPageRoute(
- page: HealthCalculatorDetailedPage(
- calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
- clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID,
- calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID,
+ return ListView.separated(
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length,
+ padding: EdgeInsets.zero,
+ itemBuilder: (BuildContext context, int index) {
+ return HealthCard(
+ icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon,
+ labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title,
+ subtitle: type == HealthCalculatorEnum.general ? generalHealthServices[index].subTitle ?? "" : womenHealthServices[index].subTitle ?? "",
+ iconColor: type == HealthCalculatorEnum.general ? generalHealthServices[index].iconColor : womenHealthServices[index].iconColor,
+ iconBgColor: type == HealthCalculatorEnum.general ? generalHealthServices[index].bgColor : womenHealthServices[index].bgColor,
+ onTap: () {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(
+ calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
+ clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID,
+ calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID,
+ ),
),
- ),
- );
- },
- );
- },
- );
+ );
+ },
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return SizedBox(height: 16.h);
+ });
}
final List generalHealthServices = [
- HealthComponentModel(title: LocaleKeys.bmiCalculator.tr(), icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi, clinicID: 108, calculationID: null),
- HealthComponentModel(title: LocaleKeys.caloriesCalculator.tr(), icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories, clinicID: null, calculationID: 2),
- HealthComponentModel(title: LocaleKeys.bmrCalculator.tr(), icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr, clinicID: null, calculationID: 3),
- HealthComponentModel(title: LocaleKeys.idealBodyWeight.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight, clinicID: null, calculationID: 4),
- HealthComponentModel(title: LocaleKeys.bodyFatCalculator.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat, clinicID: null, calculationID: 5),
- HealthComponentModel(title: LocaleKeys.carbsProteinFat.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat, clinicID: null, calculationID: 11),
+ HealthComponentModel(
+ title: LocaleKeys.bmiCalculator.tr(),
+ subTitle: LocaleKeys.bmiCalculatorDesc.tr(),
+ icon: AppAssets.bmicalc,
+ type: HealthCalculatorsTypeEnum.bmi,
+ clinicID: 108,
+ bgColor: AppColors.bmiCalculatorColor,
+ calculationID: null),
+ HealthComponentModel(
+ title: LocaleKeys.caloriesCalculator.tr(),
+ subTitle: LocaleKeys.caloriesCalculatorDesc.tr(),
+ icon: AppAssets.bmicalc,
+ type: HealthCalculatorsTypeEnum.calories,
+ clinicID: null,
+ bgColor: AppColors.caloriesCalculatorColor,
+ calculationID: 2),
+ HealthComponentModel(
+ title: LocaleKeys.bmrCalculator.tr(),
+ subTitle: LocaleKeys.bmrCalculatorDesc.tr(),
+ icon: AppAssets.bmrcalc,
+ type: HealthCalculatorsTypeEnum.bmr,
+ clinicID: null,
+ bgColor: AppColors.bmrCalculatorColor,
+ calculationID: 3),
+ HealthComponentModel(
+ title: LocaleKeys.idealBodyWeight.tr(),
+ subTitle: LocaleKeys.idealBodyWeightDesc.tr(),
+ icon: AppAssets.idealbodyweightcacl,
+ type: HealthCalculatorsTypeEnum.idealBodyWeight,
+ bgColor: AppColors.idealBodyWeightColor,
+ clinicID: null,
+ calculationID: 4),
+ HealthComponentModel(
+ title: LocaleKeys.bodyFatCalculator.tr(),
+ subTitle: LocaleKeys.bodyFatCalculatorDesc.tr(),
+ icon: AppAssets.bodyfatcalc,
+ type: HealthCalculatorsTypeEnum.bodyFat,
+ clinicID: null,
+ bgColor: AppColors.bodyFatCalculatorColor,
+ calculationID: 5),
+ HealthComponentModel(
+ title: LocaleKeys.carbsProteinFat.tr(),
+ subTitle: LocaleKeys.carbsProteinFatDesc.tr(),
+ icon: AppAssets.bmicalc,
+ type: HealthCalculatorsTypeEnum.crabsProteinFat,
+ clinicID: null,
+ bgColor: AppColors.carbsProteinFatColor,
+ calculationID: 11),
];
final List womenHealthServices = [
- HealthComponentModel(title: LocaleKeys.ovulationPeriod.tr(), icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation, clinicID: null, calculationID: 6 ),
- HealthComponentModel(title: LocaleKeys.deliveryDueDate.tr(), icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate, clinicID: null, calculationID: 6),
+ HealthComponentModel(
+ title: LocaleKeys.ovulationPeriod.tr(),
+ subTitle: LocaleKeys.ovulationPeriodDesc.tr(),
+ icon: AppAssets.ovluationperiod,
+ type: HealthCalculatorsTypeEnum.ovulation,
+ clinicID: null,
+ calculationID: 6,
+ bgColor: AppColors.ovulationPeriodColor),
+ HealthComponentModel(
+ title: LocaleKeys.deliveryDueDate.tr(),
+ subTitle: LocaleKeys.deliveryDueDateDesc.tr(),
+ icon: AppAssets.duedatecalc,
+ type: HealthCalculatorsTypeEnum.deliveryDueDate,
+ clinicID: null,
+ bgColor: AppColors.deliveryDueDateColor,
+ calculationID: 6),
];
}
diff --git a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart
index 21482e7c..e6c3d4d4 100644
--- a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart
@@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@@ -9,41 +12,55 @@ class HealthCard extends StatelessWidget {
const HealthCard({
super.key,
required this.icon,
+ this.iconColor,
+ this.iconBgColor,
required this.labelText,
+ required this.subtitle,
this.onTap,
});
final String icon;
+ final Color? iconColor;
+ final Color? iconBgColor;
final String labelText;
+ final String subtitle;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
+ AppState appState = getIt.get();
return Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.r,
- hasShadow: false,
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
- Utils.buildSvgWithAssets(
- icon: icon,
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: iconBgColor ?? Colors.transparent, borderRadius: 10.r),
+ height: 40.w,
width: 40.w,
- height: 40.h,
- fit: BoxFit.cover,
- ).toShimmer2(isShow: false, radius: 12.r),
- SizedBox(height: 14.h),
- Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Flexible(
- child: labelText.toText12(
- isBold: true,
- maxLine: 2,
- )),
- ],
+ child: Utils.buildSvgWithAssets(icon: icon, fit: BoxFit.none, height: 22.w, width: 22.w, iconColor: iconColor),
+ ),
+ SizedBox(width: 12.h),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ labelText.toText16(weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 4.h),
+ subtitle.toText12(maxLine: 2, color: AppColors.greyTextColor),
+ ],
+ ),
+ ),
+ SizedBox(width: 2.h),
+ Transform.flip(
+ flipX: appState.getLanguageCode() == "ar",
+ child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.cover, iconColor: AppColors.textColor),
),
],
).paddingAll(16.w).onPress(() {
diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart
index e9cd83eb..1e39eac8 100644
--- a/lib/theme/colors.dart
+++ b/lib/theme/colors.dart
@@ -90,6 +90,16 @@ class AppColors {
static Color get myChildVaccineCardColor => isDarkMode ? dark.myChildVaccineCardColor : const Color(0xFFFF2D78);
static Color get bookAppointment => isDarkMode ? dark.bookAppointment : const Color(0xFF415364);
+ // ── Health Calculators Colors ─────────────────────────────────────────────
+ static Color get bmiCalculatorColor => isDarkMode ? dark.bmiCalculatorColor : const Color(0xFFF43333);
+ static Color get caloriesCalculatorColor => isDarkMode ? dark.caloriesCalculatorColor : const Color(0xFF0B85F7);
+ static Color get bmrCalculatorColor => isDarkMode ? dark.bmrCalculatorColor : const Color(0xFF18C273);
+ static Color get idealBodyWeightColor => isDarkMode ? dark.idealBodyWeightColor : const Color(0xFFFF8012);
+ static Color get bodyFatCalculatorColor => isDarkMode ? dark.bodyFatCalculatorColor : const Color(0xFFFF5662);
+ static Color get carbsProteinFatColor => isDarkMode ? dark.carbsProteinFatColor : const Color(0xFF18C273);
+ static Color get ovulationPeriodColor => isDarkMode ? dark.ovulationPeriodColor : const Color(0xFFFF47A9);
+ static Color get deliveryDueDateColor => isDarkMode ? dark.deliveryDueDateColor : const Color(0xFFFF2D78);
+
// ── Water Monitor ─────────────────────────────────────────────────────────
static Color get blueColor => isDarkMode ? dark.blueColor : const Color(0xFF4EB5FF);
static Color get blueGradientColorOne => isDarkMode ? dark.blueGradientColorOne : const Color(0xFFF1F7FD);
@@ -217,6 +227,16 @@ class AppColorsDark {
Color get myChildVaccineCardColor => const Color(0xFFFF2D78);
Color get bookAppointment => const Color(0xFF415364);
+ // Health Calculators
+ Color get bmiCalculatorColor => const Color(0xFFF43333);
+ Color get caloriesCalculatorColor => const Color(0xFF0B85F7);
+ Color get bmrCalculatorColor => const Color(0xFF18C273);
+ Color get idealBodyWeightColor => const Color(0xFFFF8012);
+ Color get bodyFatCalculatorColor => const Color(0xFFFF5662);
+ Color get carbsProteinFatColor => const Color(0xFF18C273);
+ Color get ovulationPeriodColor => const Color(0xFFFF47A9);
+ Color get deliveryDueDateColor => const Color(0xFFFF2D78);
+
// Water Monitor
Color get blueColor => const Color(0xFF4EB5FF);
Color get blueGradientColorOne => const Color(0xFF101C2A);
diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart
index d47971d7..267fb5c5 100644
--- a/lib/widgets/common_bottom_sheet.dart
+++ b/lib/widgets/common_bottom_sheet.dart
@@ -20,7 +20,12 @@ class BottomSheetUtils {
showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted,
{required Function() onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool isMultiAllowed = false}) async {
if (Platform.isAndroid) {
- if (await PermissionService.isCalendarPermissionEnabled()) {
+ bool hasPermission = await PermissionService.isCalendarPermissionEnabled();
+ if (!hasPermission) {
+ PermissionStatus status = await Permission.calendarFullAccess.request();
+ hasPermission = status.isGranted;
+ }
+ if (hasPermission) {
_showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted,
onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed);
} else {