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/cloudyweather.svg b/assets/images/svg/cloudyweather.svg
new file mode 100644
index 00000000..6f8ebb47
--- /dev/null
+++ b/assets/images/svg/cloudyweather.svg
@@ -0,0 +1,49 @@
+
+
+
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/evening.svg b/assets/images/svg/evening.svg
new file mode 100644
index 00000000..7932d78d
--- /dev/null
+++ b/assets/images/svg/evening.svg
@@ -0,0 +1,9 @@
+
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/morning.svg b/assets/images/svg/morning.svg
new file mode 100644
index 00000000..0f0d9904
--- /dev/null
+++ b/assets/images/svg/morning.svg
@@ -0,0 +1,9 @@
+
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 f491be4d..7294feb4 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -1178,14 +1178,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 d9773a81..9305c378 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -1169,14 +1169,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/dependencies.dart b/lib/core/dependencies.dart
index 3b7253db..f451f0d5 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -64,6 +64,8 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
+import 'package:hmg_patient_app_new/features/weather/weather_repo.dart';
+import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
@@ -164,6 +166,7 @@ class AppDependencies {
getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => WeatherRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => HealthTrackersRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt()));
@@ -299,6 +302,8 @@ class AppDependencies {
getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
+ getIt.registerLazySingleton(() => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt()));
+
//commenting this because its already define there was on run time error because of this.
getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
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/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart
index b94d1103..8b7ce7c3 100644
--- a/lib/features/medical_file/medical_file_view_model.dart
+++ b/lib/features/medical_file/medical_file_view_model.dart
@@ -41,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier {
List patientSickLeavesViewList = [];
bool isSickLeavesSortByClinic = true;
+
// bool isSickLeavesDataNeedsReloading = true;
List patientAllergiesList = [];
@@ -165,13 +166,13 @@ class MedicalFileViewModel extends ChangeNotifier {
setIsPatientSickLeaveListLoading(bool val) {
// if (val && isSickLeavesDataNeedsReloading) {
- patientSickLeaveList.clear();
- patientSickLeavesByClinic.clear();
- patientSickLeavesByHospital.clear();
- patientSickLeavesViewList.clear();
- patientSickLeavePDFBase64 = "";
- isSickLeavesSortByClinic = true;
- isPatientSickLeaveListLoading = val;
+ patientSickLeaveList.clear();
+ patientSickLeavesByClinic.clear();
+ patientSickLeavesByHospital.clear();
+ patientSickLeavesViewList.clear();
+ patientSickLeavePDFBase64 = "";
+ isSickLeavesSortByClinic = true;
+ isPatientSickLeaveListLoading = val;
// }
notifyListeners();
}
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 73f8d331..942a51e6 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -2,11 +2,13 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.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/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';
@@ -19,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';
@@ -1109,4 +1112,409 @@ 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 {
+ try {
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
+ DateTime endDate = DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate);
+
+ // 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;
+ }
+
+ // 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());
+ }
+ }
+ }
+
+ /// 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: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}",
+ );
+
+ // Update the reminder status (inverted because we're removing)
+ setAppointmentReminder(!isEventRemoved, patientAppointmentHistoryResponseModel);
+
+ if (onSuccess != null) {
+ onSuccess(isEventRemoved);
+ }
+ } catch (e) {
+ if (onError != null) {
+ onError(e.toString());
+ }
+ }
+ }
+
+ /// 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 {
+ if (shouldCreateReminder) {
+ 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/features/weather/models/waether_cities_model.dart b/lib/features/weather/models/waether_cities_model.dart
new file mode 100644
index 00000000..6a434bff
--- /dev/null
+++ b/lib/features/weather/models/waether_cities_model.dart
@@ -0,0 +1,257 @@
+import 'dart:convert';
+
+class GetCityInfoList {
+ CategoriesNames? categoriesNames;
+ String? category;
+ String? categoryValue;
+ int? cityId;
+ String? cityName;
+ String? cityNameN;
+ ColorName? colorName;
+ String? createdOn;
+ String? id;
+ int? iniceId;
+ bool? isOrderEmpty;
+ bool? isValuesReversed;
+ bool? language;
+ double? latitude;
+ double? longitude;
+ String? name;
+ int? orderNum;
+ double? temperature;
+ String? value;
+
+ GetCityInfoList({
+ this.categoriesNames,
+ this.category,
+ this.categoryValue,
+ this.cityId,
+ this.cityName,
+ this.cityNameN,
+ this.colorName,
+ this.createdOn,
+ this.id,
+ this.iniceId,
+ this.isOrderEmpty,
+ this.isValuesReversed,
+ this.language,
+ this.latitude,
+ this.longitude,
+ this.name,
+ this.orderNum,
+ this.temperature,
+ this.value,
+ });
+
+ factory GetCityInfoList.fromRawJson(String str) => GetCityInfoList.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory GetCityInfoList.fromJson(Map json) {
+ ColorName? parseColorName(dynamic value) {
+ if (value == null) return null;
+ try {
+ return colorNameValues.map[value.toString()];
+ } catch (e) {
+ print("Unknown color name: $value");
+ return null;
+ }
+ }
+
+ CategoriesNames? parseCategoriesNames(dynamic value) {
+ if (value == null) return null;
+ try {
+ return CategoriesNames.fromJson(value as Map);
+ } catch (e) {
+ print("Error parsing CategoriesNames: $e");
+ return null;
+ }
+ }
+
+ return GetCityInfoList(
+ categoriesNames: parseCategoriesNames(json["CategoriesNames"]),
+ category: json["Category"],
+ categoryValue: json["CategoryValue"],
+ cityId: json["CityID"],
+ cityName: json["CityName"],
+ cityNameN: json["CityNameN"],
+ colorName: parseColorName(json["ColorName"]),
+ createdOn: json["CreatedOn"],
+ id: json["ID"],
+ iniceId: json["IniceID"],
+ isOrderEmpty: json["IsOrderEmpty"],
+ isValuesReversed: json["IsValuesReversed"],
+ language: json["Language"],
+ latitude: json["Latitude"]?.toDouble(),
+ longitude: json["Longitude"]?.toDouble(),
+ name: json["Name"],
+ orderNum: json["OrderNum"],
+ temperature: json["Temperature"]?.toDouble(),
+ value: json["Value"],
+ );
+ }
+
+ Map toJson() => {
+ "CategoriesNames": categoriesNames?.toJson(),
+ "Category": category,
+ "CategoryValue": categoryValue,
+ "CityID": cityId,
+ "CityName": cityName,
+ "CityNameN": cityNameN,
+ "ColorName": colorNameValues.reverse[colorName],
+ "CreatedOn": createdOn,
+ "ID": id,
+ "IniceID": iniceId,
+ "IsOrderEmpty": isOrderEmpty,
+ "IsValuesReversed": isValuesReversed,
+ "Language": language,
+ "Latitude": latitude,
+ "Longitude": longitude,
+ "Name": name,
+ "OrderNum": orderNum,
+ "Temperature": temperature,
+ "Value": value,
+ };
+}
+
+class CategoriesNames {
+ Category1? category1;
+ Category2? category2;
+ Category3? category3;
+ Category4? category4;
+ Category5? category5;
+
+ CategoriesNames({
+ this.category1,
+ this.category2,
+ this.category3,
+ this.category4,
+ this.category5,
+ });
+
+ factory CategoriesNames.fromRawJson(String str) => CategoriesNames.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory CategoriesNames.fromJson(Map json) {
+ T? parseEnum(dynamic value, EnumValues enumValues, String enumName) {
+ if (value == null) return null;
+ try {
+ return enumValues.map[value.toString()];
+ } catch (e) {
+ print("Unknown $enumName value: $value");
+ return null;
+ }
+ }
+
+ return CategoriesNames(
+ category1: parseEnum(json["Category1"], category1Values, "Category1"),
+ category2: parseEnum(json["Category2"], category2Values, "Category2"),
+ category3: parseEnum(json["Category3"], category3Values, "Category3"),
+ category4: parseEnum(json["Category4"], category4Values, "Category4"),
+ category5: parseEnum(json["Category5"], category5Values, "Category5"),
+ );
+ }
+
+ Map toJson() => {
+ "Category1": category1Values.reverse[category1],
+ "Category2": category2Values.reverse[category2],
+ "Category3": category3Values.reverse[category3],
+ "Category4": category4Values.reverse[category4],
+ "Category5": category5Values.reverse[category5],
+ };
+}
+
+enum Category1 {
+ AT_EXTREME_RISK,
+ CATEGORY1_EXTREME,
+ EXTREME,
+ POOR
+}
+
+final category1Values = EnumValues({
+ "At Extreme Risk": Category1.AT_EXTREME_RISK,
+ "Extreme\u000d\n": Category1.CATEGORY1_EXTREME,
+ "Extreme": Category1.EXTREME,
+ "Poor": Category1.POOR
+});
+
+enum Category2 {
+ AT_HIGH_RISK,
+ CATEGORY2_VERY_HIGH,
+ FAIR,
+ VERY_HIGH
+}
+
+final category2Values = EnumValues({
+ "At High Risk": Category2.AT_HIGH_RISK,
+ "Very High\u000d\n": Category2.CATEGORY2_VERY_HIGH,
+ "Fair": Category2.FAIR,
+ "Very High": Category2.VERY_HIGH
+});
+
+enum Category3 {
+ AT_RISK,
+ CATEGORY3_HIGH,
+ GOOD,
+ HIGH
+}
+
+final category3Values = EnumValues({
+ "At Risk": Category3.AT_RISK,
+ "High\u000d\n": Category3.CATEGORY3_HIGH,
+ "Good": Category3.GOOD,
+ "High": Category3.HIGH
+});
+
+enum Category4 {
+ CATEGORY4_MODERATE,
+ MODERATE,
+ NEUTRAL,
+ VERY_GOOD
+}
+
+final category4Values = EnumValues({
+ "Moderate\u000d\n": Category4.CATEGORY4_MODERATE,
+ "Moderate": Category4.MODERATE,
+ "Neutral": Category4.NEUTRAL,
+ "Very Good": Category4.VERY_GOOD
+});
+
+enum Category5 {
+ BENEFICIAL,
+ CATEGORY5_LOW,
+ EXCELLENT,
+ LOW
+}
+
+final category5Values = EnumValues({
+ "Beneficial": Category5.BENEFICIAL,
+ "Low\u000d\n": Category5.CATEGORY5_LOW,
+ "Excellent": Category5.EXCELLENT,
+ "Low": Category5.LOW
+});
+
+enum ColorName {
+ GREEN,
+ ORANGE,
+ RED
+}
+
+final colorNameValues = EnumValues({
+ "green": ColorName.GREEN,
+ "orange": ColorName.ORANGE,
+ "red": ColorName.RED
+});
+
+class EnumValues {
+ Map map;
+ late Map reverseMap;
+
+ EnumValues(this.map);
+
+ Map get reverse {
+ reverseMap = map.map((k, v) => MapEntry(v, k));
+ return reverseMap;
+ }
+}
diff --git a/lib/features/weather/weather_repo.dart b/lib/features/weather/weather_repo.dart
new file mode 100644
index 00000000..26bf0a32
--- /dev/null
+++ b/lib/features/weather/weather_repo.dart
@@ -0,0 +1,72 @@
+import 'package:dartz/dartz.dart';
+import 'package:hmg_patient_app_new/core/api/api_client.dart';
+import 'package:hmg_patient_app_new/core/api_consts.dart';
+import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
+import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
+import 'package:hmg_patient_app_new/core/location_util.dart';
+import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+abstract class WeatherRepo {
+ Future>>> getCityInfo();
+}
+
+class WeatherRepoImp implements WeatherRepo {
+ final ApiClient apiClient;
+ final LoggerService loggerService;
+
+ WeatherRepoImp({required this.loggerService, required this.apiClient});
+
+ @override
+ Future>>> getCityInfo() async {
+ Map request = {};
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ WEATHER_INDICATOR,
+ body: request,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ loggerService.logInfo("Weather API Response: $response");
+ final list = response['GetCityInfo_List'];
+
+ if (list == null || list.isEmpty) {
+ loggerService.logInfo("Weather API returned empty or null list");
+ // Return empty list if no city info
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: [],
+ );
+ return;
+ }
+
+ loggerService.logInfo("Parsing ${list.length} city info items");
+ final cityInfoList = list.map((item) => GetCityInfoList.fromJson(item as Map)).toList().cast();
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: cityInfoList,
+ );
+ } catch (e) {
+ loggerService.logError("Error parsing weather data: $e");
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+}
diff --git a/lib/features/weather/weather_view_model.dart b/lib/features/weather/weather_view_model.dart
new file mode 100644
index 00000000..0e3a3285
--- /dev/null
+++ b/lib/features/weather/weather_view_model.dart
@@ -0,0 +1,55 @@
+import 'package:flutter/cupertino.dart';
+import 'package:hmg_patient_app_new/core/location_util.dart';
+import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart';
+import 'package:hmg_patient_app_new/features/weather/weather_repo.dart';
+import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+
+class WeatherMonitorViewModel extends ChangeNotifier {
+ WeatherRepo weatherRepo;
+ ErrorHandlerService errorHandlerService;
+ LocationUtils locationUtils;
+
+ WeatherMonitorViewModel({required this.weatherRepo, required this.errorHandlerService, required this.locationUtils});
+
+ final List _cityInfoList = [];
+ bool isLoading = false;
+
+ List get cityInfoList => _cityInfoList;
+
+ Future fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ isLoading = true;
+ _cityInfoList.clear();
+
+ locationUtils.getLocation(
+ isShowConfirmDialog: true,
+ onSuccess: (position) async {
+ final result = await weatherRepo.getCityInfo();
+ result.fold(
+ (failure) async {
+ isLoading = false;
+ notifyListeners();
+ await errorHandlerService.handleError(failure: failure);
+ if (onError != null) {
+ onError(failure.toString());
+ }
+ },
+ (apiResponse) {
+ isLoading = false;
+ if (apiResponse.messageStatus == 2) {
+ notifyListeners();
+ if (onError != null) {
+ onError(apiResponse.errorMessage ?? "Unknown error");
+ }
+ } else if (apiResponse.messageStatus == 1) {
+ final cities = apiResponse.data ?? [];
+ _cityInfoList.addAll(cities);
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ });
+ }
+}
diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart
index 43248236..2b55cfc1 100644
--- a/lib/generated/codegen_loader.g.dart
+++ b/lib/generated/codegen_loader.g.dart
@@ -2910,14 +2910,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/main.dart b/lib/main.dart
index e9b599c9..6f086c34 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -10,6 +10,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/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
@@ -242,6 +243,9 @@ void main() async {
),
ChangeNotifierProvider(
create: (_) => getIt.get(),
+ ) ,
+ ChangeNotifierProvider(
+ create: (_) => getIt.get(),
)
], child: MyApp()),
), // Wrap your app
diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart
index 78532cce..ff656c72 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';
@@ -33,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';
@@ -47,6 +46,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:lottie/lottie.dart';
import 'package:map_launcher/map_launcher.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
@@ -96,18 +96,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();
@@ -307,11 +343,13 @@ class _AppointmentDetailsPageState extends State {
},
onError: (err) {
LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
+ showCommonBottomSheetWithoutHeight(context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
- title: "", isCloseButtonVisible: true, isDismissible: false, isFullScreen: false);
+ title: "",
+ isCloseButtonVisible: true,
+ isDismissible: false,
+ isFullScreen: false);
});
}),
callBackFunc: () {},
@@ -326,7 +364,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(
@@ -470,22 +559,24 @@ class _AppointmentDetailsPageState extends State {
],
),
SizedBox(height: 8.h),
- bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap(
- direction: Axis.horizontal,
- spacing: 8.w,
- runSpacing: 8.h,
- children: [
- AppCustomChipWidget(
- labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
- ? "Floor: Ground Floor"
+ bookAppointmentsViewModel.appointmentNearestGateResponseModel != null
+ ? Wrap(
+ direction: Axis.horizontal,
+ spacing: 8.w,
+ runSpacing: 8.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
+ ? "Floor: Ground Floor"
: "${LocaleKeys.floor.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}",
).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
- AppCustomChipWidget(
- labelText:
+ AppCustomChipWidget(
+ labelText:
"${LocaleKeys.nearestGate.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
- ],
- ) : SizedBox.shrink(),
+ ],
+ )
+ : SizedBox.shrink(),
],
),
),
@@ -511,22 +602,27 @@ 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 {
- CalenderUtilsNew calender = CalenderUtilsNew.instance;
+ CalenderUtilsNew calender = CalenderUtilsNew.instance;
bool isEventAddedOrRemoved = false;
- if(newValue == true){
+ if (newValue == true) {
DateTime startDate = DateTime.now();
- DateTime endDate = DateUtil.convertStringToDate(widget
- .patientAppointmentHistoryResponseModel.appointmentDate);
+ DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate);
BottomSheetUtils().showReminderBottomSheet(
context,
endDate,
- widget.patientAppointmentHistoryResponseModel.doctorNameObj??"",
- "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"",
+ widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
+ "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "",
"",
"",
title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
@@ -544,27 +640,80 @@ class _AppointmentDetailsPageState extends State {
"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
- );
+ 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}", );
+ } 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),
),
@@ -582,106 +731,116 @@ class _AppointmentDetailsPageState extends State {
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
- shrinkWrap: true,
- children: [
- MedicalFileCard(
- label: LocaleKeys.labResults.tr(context: context),
- textColor: AppColors.blackColor,
- backgroundColor: AppColors.whiteColor,
- svgIcon: AppAssets.lab_result_icon,
- isLargeText: true,
- iconSize: 36.w,
- ).onPress(() async {
- LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context));
- await labViewModel.getLabResultsByAppointmentNo(
- appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
- projectID: widget.patientAppointmentHistoryResponseModel.projectID,
- clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
- doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
- doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!,
- clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!,
- projectName: widget.patientAppointmentHistoryResponseModel.projectName!,
- appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
- onSuccess: (val) {
- LoaderBottomSheet.hideLoader();
- },
- onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- },
- );
- }),
- MedicalFileCard(
- label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}",
- textColor: AppColors.blackColor,
- backgroundColor: AppColors.whiteColor,
- svgIcon: AppAssets.radiology_icon,
- isLargeText: true,
- iconSize: 36.w,
- ).onPress(() async {
- LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context));
- await radiologyViewModel.getPatientRadiologyOrdersByAppointment(
- appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
- projectID: widget.patientAppointmentHistoryResponseModel.projectID,
- onSuccess: (val) {
- LoaderBottomSheet.hideLoader();
- },
- onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- },
- );
- }),
- MedicalFileCard(
- label: LocaleKeys.prescriptions.tr(context: context),
- textColor: AppColors.blackColor,
- backgroundColor: AppColors.whiteColor,
- svgIcon: AppAssets.prescription_item_icon,
- isLargeText: true,
- iconSize: 36.w,
- ).onPress(() async {
- LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context));
- await prescriptionsViewModel.getPrescriptionDetails(
- getPrescriptionRequestModel(),
- onSuccess: (val) {
- LoaderBottomSheet.hideLoader();
- if (val.data.isNotEmpty) {
- PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel(
- doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
- doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
- appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate,
- clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName,
- decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate,
- name: widget.patientAppointmentHistoryResponseModel.projectName,
- isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false,
- projectID: widget.patientAppointmentHistoryResponseModel.projectID,
- clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
- doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
- setupID: widget.patientAppointmentHistoryResponseModel.setupID,
- isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient,
- appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
- episodeID: widget.patientAppointmentHistoryResponseModel.episodeID,
- dischargeNo: 0,
+ shrinkWrap: true,
+ children: [
+ MedicalFileCard(
+ label: LocaleKeys.labResults.tr(context: context),
+ textColor: AppColors.blackColor,
+ backgroundColor: AppColors.whiteColor,
+ svgIcon: AppAssets.lab_result_icon,
+ isLargeText: true,
+ iconSize: 36.w,
+ ).onPress(() async {
+ LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context));
+ await labViewModel.getLabResultsByAppointmentNo(
+ appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
+ projectID: widget.patientAppointmentHistoryResponseModel.projectID,
+ clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
+ doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
+ doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!,
+ clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!,
+ projectName: widget.patientAppointmentHistoryResponseModel.projectName!,
+ appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
+ onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
);
- Navigator.of(context).push(
- CustomPageRoute(
- page: PrescriptionDetailPage(
- isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
- ),
+ },
+ );
+ }),
+ MedicalFileCard(
+ label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}",
+ textColor: AppColors.blackColor,
+ backgroundColor: AppColors.whiteColor,
+ svgIcon: AppAssets.radiology_icon,
+ isLargeText: true,
+ iconSize: 36.w,
+ ).onPress(() async {
+ LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context));
+ await radiologyViewModel.getPatientRadiologyOrdersByAppointment(
+ appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
+ projectID: widget.patientAppointmentHistoryResponseModel.projectID,
+ onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
);
- } else {
+ },
+ );
+ }),
+ MedicalFileCard(
+ label: LocaleKeys.prescriptions.tr(context: context),
+ textColor: AppColors.blackColor,
+ backgroundColor: AppColors.whiteColor,
+ svgIcon: AppAssets.prescription_item_icon,
+ isLargeText: true,
+ iconSize: 36.w,
+ ).onPress(() async {
+ LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context));
+ await prescriptionsViewModel.getPrescriptionDetails(
+ getPrescriptionRequestModel(),
+ onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ if (val.data.isNotEmpty) {
+ PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel(
+ doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
+ doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
+ appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate,
+ clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName,
+ decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate,
+ name: widget.patientAppointmentHistoryResponseModel.projectName,
+ isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false,
+ projectID: widget.patientAppointmentHistoryResponseModel.projectID,
+ clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
+ doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
+ setupID: widget.patientAppointmentHistoryResponseModel.setupID,
+ isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient,
+ appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
+ episodeID: widget.patientAppointmentHistoryResponseModel.episodeID,
+ dischargeNo: 0,
+ );
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
+ ),
+ );
+ } else {
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ }
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)),
@@ -689,40 +848,29 @@ class _AppointmentDetailsPageState extends State {
isFullScreen: false,
isCloseButtonVisible: true,
);
- }
- },
- onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- },
- );
- // Navigator.of(context).push(
- // CustomPageRoute(
- // page: VaccineListPage(),
- // ),
- // );
- }),
- MedicalFileCard(
- label: LocaleKeys.doctorResponses.tr(context: context),
- textColor: AppColors.blackColor,
- backgroundColor: AppColors.whiteColor,
- svgIcon: AppAssets.ask_doctor_medical_file_icon,
- isLargeText: true,
- iconSize: 36.w,
- ).onPress(() {
- getIt.get().initAskDoctorViewModel();
- getIt.get().getDoctorResponses();
- Navigator.of(context).push(
- CustomPageRoute(
- page: DoctorResponsePage(),
- ),
- );
+ },
+ );
+ // Navigator.of(context).push(
+ // CustomPageRoute(
+ // page: VaccineListPage(),
+ // ),
+ // );
+ }),
+ MedicalFileCard(
+ label: LocaleKeys.doctorResponses.tr(context: context),
+ textColor: AppColors.blackColor,
+ backgroundColor: AppColors.whiteColor,
+ svgIcon: AppAssets.ask_doctor_medical_file_icon,
+ isLargeText: true,
+ iconSize: 36.w,
+ ).onPress(() {
+ getIt.get().initAskDoctorViewModel();
+ getIt.get().getDoctorResponses();
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: DoctorResponsePage(),
+ ),
+ );
}),
myAppointmentsVM.isAppointmentRatedResponseLoading
? MedicalFileCard(
@@ -993,8 +1141,7 @@ class _AppointmentDetailsPageState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 ||
- widget.patientAppointmentHistoryResponseModel.nextAction == 20)
+ if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -1002,10 +1149,7 @@ class _AppointmentDetailsPageState extends State {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true),
- Utils.getPaymentAmountWithSymbol(
- widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true),
- AppColors.blackColor,
- 13,
+ Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
],
),
@@ -1013,9 +1157,7 @@ class _AppointmentDetailsPageState extends State {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Expanded(
- child: LocaleKeys.upcomingPaymentNow
- .tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)),
+ Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)),
"VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64),
],
),
@@ -1030,10 +1172,7 @@ class _AppointmentDetailsPageState extends State {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Utils.getPaymentAmountWithSymbol(
- widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true),
- AppColors.blackColor,
- 17,
+ Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17,
isSaudiCurrency: true),
],
),
@@ -1057,11 +1196,11 @@ class _AppointmentDetailsPageState extends State {
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 16.f,
- fontWeight: FontWeight.w600,
+ fontWeight: FontWeight.w600,
borderRadius: 12.r,
- padding: EdgeInsets.symmetric(horizontal: 10.w),
- height: 50.h,
- icon: AppAssets.add_icon,
+ padding: EdgeInsets.symmetric(horizontal: 10.w),
+ height: 50.h,
+ icon: AppAssets.add_icon,
iconColor: Colors.white,
iconSize: 18.h,
).paddingSymmetrical(16.h, 24.h)
@@ -1233,8 +1372,7 @@ class _AppointmentDetailsPageState extends State {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
Navigator.of(context).pop();
- }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false,
- isSuccessDialog: true);
+ }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true);
});
// LoaderBottomSheet.hideLoader();
case 15:
@@ -1254,43 +1392,45 @@ class _AppointmentDetailsPageState extends State {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
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),
- ),
- ],
- ),
+ Lottie.asset(AppAnimations.warningAnimation,
+ repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
+ SizedBox(height: 12,),
+ LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(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
CustomButton(
text: LocaleKeys.acknowledged.tr(context: context),
diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
index 293f58ac..593746cb 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 689e6cf3..86702dbb 100644
--- a/lib/presentation/book_appointment/review_appointment_page.dart
+++ b/lib/presentation/book_appointment/review_appointment_page.dart
@@ -90,12 +90,14 @@ class _ReviewAppointmentPageState extends State {
.toString()
.toText16(isBold: true, maxlines: 1),
SizedBox(width: 12.w),
- (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty )? Image.network(
- bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
- width: 20.h,
- height: 15.h,
- fit: BoxFit.cover,
- ) : SizedBox.shrink(),
+ (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty)
+ ? Image.network(
+ bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
+ width: 20.h,
+ height: 15.h,
+ fit: BoxFit.cover,
+ )
+ : SizedBox.shrink(),
],
),
SizedBox(height: 2.h),
@@ -162,7 +164,9 @@ class _ReviewAppointmentPageState extends State {
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"),
- AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
+ AppCustomChipWidget(
+ labelText:
+ "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
],
),
],
@@ -191,19 +195,19 @@ class _ReviewAppointmentPageState extends State {
bookAppointmentsViewModel.appointmentNearestGateResponseModel != null
? Wrap(
direction: Axis.horizontal,
- spacing: 8.w,
- runSpacing: 8.h,
- children: [
- AppCustomChipWidget(
- labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
- ? "Floor: Ground Floor"
+ spacing: 8.w,
+ runSpacing: 8.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: bookAppointmentsVM.isAppointmentNearestGateLoading
+ ? "Floor: Ground Floor"
: "${LocaleKeys.floor.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}",
).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
- AppCustomChipWidget(
- labelText:
+ AppCustomChipWidget(
+ labelText:
"${LocaleKeys.nearestGate.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
- ],
+ ],
)
: SizedBox.shrink(),
],
@@ -317,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) {
@@ -329,23 +333,16 @@ class _ReviewAppointmentPageState extends State {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr());
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()),
- callBackFunc: () {
- bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
- bookAppointmentsViewModel.setIsLiveCareSchedule(false);
- Navigator.pushAndRemoveUntil(
- context,
- CustomPageRoute(
- page: LandingNavigation(),
- ),
- (r) => false);
- },
- isFullScreen: false,
- isCloseButtonVisible: false,
- isAutoDismiss: true
- );
+ showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () {
+ bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
+ bookAppointmentsViewModel.setIsLiveCareSchedule(false);
+ Navigator.pushAndRemoveUntil(
+ context,
+ CustomPageRoute(
+ page: LandingNavigation(),
+ ),
+ (r) => false);
+ }, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true);
});
});
} else {
@@ -368,23 +365,11 @@ 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: () {
- bookAppointmentsViewModel.setIsLiveCareSchedule(false);
- bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
- Navigator.pushAndRemoveUntil(
- context,
- CustomPageRoute(
- page: LandingNavigation(),
- ),
- (r) => false);
- },
- isFullScreen: false,
- isCloseButtonVisible: false,
- isAutoDismiss: true
- );
+ showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () {
+ bookAppointmentsViewModel.setIsLiveCareSchedule(false);
+ bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
+ 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/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart
index 770a5cbe..c92d47e5 100644
--- a/lib/presentation/hmg_services/services_page.dart
+++ b/lib/presentation/hmg_services/services_page.dart
@@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
+import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@@ -19,6 +20,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_s
import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
+import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
@@ -47,47 +49,46 @@ import '../../core/dependencies.dart' show getIt;
import '../../features/qr_parking/qr_parking_view_model.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
-class ServicesPage extends StatelessWidget {
+class ServicesPage extends StatefulWidget {
bool showBackIcon;
ServicesPage({super.key, this.showBackIcon = true});
+ @override
+ State createState() => _ServicesPageState();
+}
+
+class _ServicesPageState extends State {
late BloodDonationViewModel bloodDonationViewModel;
+
late MedicalFileViewModel medicalFileViewModel;
+ late WeatherMonitorViewModel weatherVM;
+
late final List hmgServices = [
HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async {
// if (getIt.get().isAuthenticated) {
- // getIt.get().flushData();
- // getIt.get().getTransportationOrders(
- // showLoader: false,
- // );
- // getIt.get().getRRTOrders(
- // showLoader: false,
- // );
- Navigator.of(getIt.get().navigatorKey.currentContext!).push(
- CustomPageRoute(
- page: EmergencyServicesPage(),
- settings: const RouteSettings(name: '/EmergencyServicesPage'),
- ),
- );
+ // getIt.get().flushData();
+ // getIt.get().getTransportationOrders(
+ // showLoader: false,
+ // );
+ // getIt.get().getRRTOrders(
+ // showLoader: false,
+ // );
+ Navigator.of(getIt.get().navigatorKey.currentContext!).push(
+ CustomPageRoute(
+ page: EmergencyServicesPage(),
+ settings: const RouteSettings(name: '/EmergencyServicesPage'),
+ ),
+ );
// } else {
// await getIt.get().onLoginPressed();
// }
}),
- HmgServicesComponentModel(
- 11,
- LocaleKeys.bookAppointment.tr(),
- "",
- AppAssets.appointment_calendar_icon,
- bgColor: AppColors.bookAppointment,
- true,
- route: null,
- onTap: () {
- getIt.get().onTabChanged(0);
- Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage()));
- }
- ),
+ HmgServicesComponentModel(11, LocaleKeys.bookAppointment.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () {
+ getIt.get().onTabChanged(0);
+ Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage()));
+ }),
HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async {
if (getIt.get().isAuthenticated) {
getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage);
@@ -164,33 +165,31 @@ class ServicesPage extends StatelessWidget {
}
}),
HmgServicesComponentModel(
- 3,
- LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!),
- "".needTranslation,
- AppAssets.blood_donation_icon,
- bgColor: AppColors.bloodDonationCardColor,
- true,
- route: null,
- onTap: () {
- if (getIt.get().isAuthenticated) {
- Navigator.of(getIt().navigatorKey.currentContext!).push(
- CustomPageRoute(
- page: BloodDonationPage(),
- ),
- );
- } else {
- Navigator.of(getIt().navigatorKey.currentContext!).push(
- CustomPageRoute(
- page: ServiceInfoPage(
- serviceName: LocaleKeys.bloodDonation.tr(),
- serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(),
- serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(),
- serviceImage: AppAssets.bloodDonationService),
- ),
- );
- }
+ 3,
+ LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!),
+ "".needTranslation,
+ AppAssets.blood_donation_icon,
+ bgColor: AppColors.bloodDonationCardColor,
+ true,
+ route: null, onTap: () {
+ if (getIt.get().isAuthenticated) {
+ Navigator.of(getIt().navigatorKey.currentContext!).push(
+ CustomPageRoute(
+ page: BloodDonationPage(),
+ ),
+ );
+ } else {
+ Navigator.of(getIt().navigatorKey.currentContext!).push(
+ CustomPageRoute(
+ page: ServiceInfoPage(
+ serviceName: LocaleKeys.bloodDonation.tr(),
+ serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(),
+ serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(),
+ serviceImage: AppAssets.bloodDonationService),
+ ),
+ );
}
- ),
+ }),
HmgServicesComponentModel(
113,
LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!),
@@ -375,21 +374,100 @@ class ServicesPage extends StatelessWidget {
),
];
+ @override
+ void initState() {
+ // TODO: implement initState
+ super.initState();
+ weatherVM = getIt();
+ weatherVM.fetchCityInfoList();
+ }
+
+
+
@override
Widget build(BuildContext context) {
bloodDonationViewModel = Provider.of(context);
medicalFileViewModel = Provider.of(context);
-
hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.exploreServices.tr(),
- isLeading: showBackIcon,
+ isLeading: widget.showBackIcon,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Row(
+ children: [
+ Flexible(
+ child: Container(
+ padding: EdgeInsets.all(16.w),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: "assets/images/svg/cloudyweather.svg", width: 64.w, height: 64.w).onPress(() {
+ // locationUtils?.getCurrentLocation();
+ print(getIt.get().userLat);
+ print(getIt.get().userLong);
+ }),
+ SizedBox(width: 12.w),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ "August 19, 2025 - Tuesday, 11:25 ".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
+ SizedBox(
+ height: 4.w,
+ ),
+ "Cloudy 38oC".toText16(isBold: true, weight: FontWeight.w600),
+ SizedBox(
+ height: 4.w,
+ ),
+ "Health tips based on current weather".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)
+ ],
+ ),
+ ],
+ ),
+ SizedBox(height: 16.w),
+ Container(
+ padding: EdgeInsets.all(16.w),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.lightGrayBGColor,
+ borderRadius: 10.r,
+ hasShadow: false,
+ ),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: "assets/images/svg/morning.svg", width: 24.w, height: 24.h),
+ SizedBox(width: 4.w),
+ DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
+ ],
+ ),
+ SizedBox(width: 25.w),
+ DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
+ SizedBox(width: 25.w),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: "assets/images/svg/evening.svg", width: 24.w, height: 24.h),
+ SizedBox(width: 4.w),
+ DateTime.now().toLocal().toString().substring(11, 16).toText14(isBold: true),
+ ],
+ ),
+ ],
+ ).expanded,
+ ).paddingSymmetrical(0.w, 8.w)
+ ],
+ ),
+ ),
+ )
+ ],
+ ).paddingSymmetrical(24.w, 0.w),
SizedBox(height: 16.h),
LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
@@ -440,173 +518,176 @@ class ServicesPage extends StatelessWidget {
),
),
SizedBox(height: 24.h),
- getIt.get().isAuthenticated ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
- SizedBox(height: 16.h),
- Row(
- children: [
- Expanded(
- child: Container(
- height: 183.h,
- width: 183.h,
- padding: EdgeInsets.all(16.w),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.r,
- hasShadow: false,
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- spacing: 8.w,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false),
- LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded,
- Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
- ],
- ),
- Spacer(),
- getIt.get().isAuthenticated
- ? Consumer(builder: (context, habibWalletVM, child) {
- return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), isExpanded: false, letterSpacing: -1)
- .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h);
- })
- : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2),
- Spacer(),
- getIt.get().isAuthenticated
- ? CustomButton(
- height: 40.h,
- icon: AppAssets.recharge_icon,
- iconSize: 24.w,
- iconColor: AppColors.infoColor,
- textColor: AppColors.infoColor,
- text: LocaleKeys.recharge.tr(),
- borderWidth: 0.w,
- isBold: true,
- borderColor: Colors.transparent,
- backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08),
- padding: EdgeInsets.all(8.w),
- fontSize: 14.f,
- onPressed: () {
- Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
- },
- )
- : SizedBox.shrink(),
- ],
- ).onPress(() async {
- if (getIt.get().isAuthenticated) {
- Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage()));
- } else {
- await getIt.get().onLoginPressed();
- }
- }),
- ),
- ),
- SizedBox(width: 16.w),
- Expanded(
- child: Container(
- height: 183.h,
- width: 183.h,
- padding: EdgeInsets.all(16.w),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.r,
- hasShadow: false,
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- spacing: 8.w,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false),
- LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded,
- Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
- ],
+ getIt.get().isAuthenticated
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
+ SizedBox(height: 16.h),
+ Row(
+ children: [
+ Expanded(
+ child: Container(
+ height: 183.h,
+ width: 183.h,
+ padding: EdgeInsets.all(16.w),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.r,
+ hasShadow: false,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ spacing: 8.w,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false),
+ LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded,
+ Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
+ ],
+ ),
+ Spacer(),
+ getIt.get().isAuthenticated
+ ? Consumer(builder: (context, habibWalletVM, child) {
+ return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)),
+ isExpanded: false, letterSpacing: -1)
+ .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h);
+ })
+ : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2),
+ Spacer(),
+ getIt.get().isAuthenticated
+ ? CustomButton(
+ height: 40.h,
+ icon: AppAssets.recharge_icon,
+ iconSize: 24.w,
+ iconColor: AppColors.infoColor,
+ textColor: AppColors.infoColor,
+ text: LocaleKeys.recharge.tr(),
+ borderWidth: 0.w,
+ isBold: true,
+ borderColor: Colors.transparent,
+ backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08),
+ padding: EdgeInsets.all(8.w),
+ fontSize: 14.f,
+ onPressed: () {
+ Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
+ },
+ )
+ : SizedBox.shrink(),
+ ],
+ ).onPress(() async {
+ if (getIt.get().isAuthenticated) {
+ Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage()));
+ } else {
+ await getIt.get().onLoginPressed();
+ }
+ }),
),
- Spacer(),
- getIt.get().isAuthenticated
- ? Wrap(
- spacing: -12.h,
- // runSpacing: 0.h,
- children: [
- Utils.buildImgWithAssets(
- icon: AppAssets.babyGirlImg,
- height: 32.h,
- width: 32.w,
- border: 1,
- fit: BoxFit.contain,
- borderRadius: 50.r,
- ),
- Utils.buildImgWithAssets(
- icon: AppAssets.femaleImg,
- height: 32.h,
- width: 32.w,
- border: 1,
- borderRadius: 50.r,
- fit: BoxFit.contain,
- ),
- Utils.buildImgWithAssets(
- icon: AppAssets.maleImg,
- height: 32.h,
- width: 32.w,
- border: 1,
- borderRadius: 50.r,
- fit: BoxFit.contain,
- ),
- ],
- )
- : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2),
- Spacer(),
- getIt.get().isAuthenticated
- ? CustomButton(
- height: 40.h,
- icon: AppAssets.add_icon,
- iconSize: 24.h,
- iconColor: AppColors.primaryRedColor,
- textColor: AppColors.primaryRedColor,
- text: LocaleKeys.addMember.tr(),
- borderWidth: 0.w,
- isBold: true,
- borderColor: Colors.transparent,
- backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
- padding: EdgeInsets.all(8.w),
- fontSize: 14.f,
- onPressed: () {
- DialogService dialogService = getIt.get();
- medicalFileViewModel.clearAuthValues();
- dialogService.showAddFamilyFileSheet(
- label: LocaleKeys.addFamilyMember.tr(),
- message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
- onVerificationPress: () {
- medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
- });
- },
- )
- : SizedBox.shrink(),
- ],
- ).onPress(() async {
- if (getIt.get().isAuthenticated) {
- Navigator.of(context).push(
- CustomPageRoute(
- page: MedicalFilePage(),
+ ),
+ SizedBox(width: 16.w),
+ Expanded(
+ child: Container(
+ height: 183.h,
+ width: 183.h,
+ padding: EdgeInsets.all(16.w),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.r,
+ hasShadow: false,
),
- );
- } else {
- await getIt.get().onLoginPressed();
- }
- }),
- ),
- ),
- ],
- ).paddingSymmetrical(24.w, 0),
- ],
- ) : SizedBox(),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ spacing: 8.w,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false),
+ LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded,
+ Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
+ ],
+ ),
+ Spacer(),
+ getIt.get().isAuthenticated
+ ? Wrap(
+ spacing: -12.h,
+ // runSpacing: 0.h,
+ children: [
+ Utils.buildImgWithAssets(
+ icon: AppAssets.babyGirlImg,
+ height: 32.h,
+ width: 32.w,
+ border: 1,
+ fit: BoxFit.contain,
+ borderRadius: 50.r,
+ ),
+ Utils.buildImgWithAssets(
+ icon: AppAssets.femaleImg,
+ height: 32.h,
+ width: 32.w,
+ border: 1,
+ borderRadius: 50.r,
+ fit: BoxFit.contain,
+ ),
+ Utils.buildImgWithAssets(
+ icon: AppAssets.maleImg,
+ height: 32.h,
+ width: 32.w,
+ border: 1,
+ borderRadius: 50.r,
+ fit: BoxFit.contain,
+ ),
+ ],
+ )
+ : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2),
+ Spacer(),
+ getIt.get().isAuthenticated
+ ? CustomButton(
+ height: 40.h,
+ icon: AppAssets.add_icon,
+ iconSize: 24.h,
+ iconColor: AppColors.primaryRedColor,
+ textColor: AppColors.primaryRedColor,
+ text: LocaleKeys.addMember.tr(),
+ borderWidth: 0.w,
+ isBold: true,
+ borderColor: Colors.transparent,
+ backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
+ padding: EdgeInsets.all(8.w),
+ fontSize: 14.f,
+ onPressed: () {
+ DialogService dialogService = getIt.get();
+ medicalFileViewModel.clearAuthValues();
+ dialogService.showAddFamilyFileSheet(
+ label: LocaleKeys.addFamilyMember.tr(),
+ message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
+ onVerificationPress: () {
+ medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
+ });
+ },
+ )
+ : SizedBox.shrink(),
+ ],
+ ).onPress(() async {
+ if (getIt.get().isAuthenticated) {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: MedicalFilePage(),
+ ),
+ );
+ } else {
+ await getIt.get().onLoginPressed();
+ }
+ }),
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(24.w, 0),
+ ],
+ )
+ : SizedBox(),
SizedBox(height: 24.h),
LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
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 {