From 697642143c85be82cbd5410cd6630323c7097334 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Sat, 18 Apr 2026 14:04:10 +0300 Subject: [PATCH] Translation & Calender --- assets/langs/ar-SA.json | 16 +++-- assets/langs/en-US.json | 8 ++- lib/core/utils/calender_utils_new.dart | 23 +++--- .../blood_donation_view_model.dart | 9 +-- .../utils/appointment_type.dart | 2 +- .../prescriptions_view_model.dart | 8 ++- lib/generated/locale_keys.g.dart | 6 ++ .../appointment_details_page.dart | 1 + .../habib_wallet/recharge_wallet_page.dart | 2 +- .../hmg_services/services_page.dart | 2 +- .../medical_file/medical_file_page.dart | 2 +- .../prescriptions/prescription_item_view.dart | 71 +++++++++++++++++++ lib/widgets/common_bottom_sheet.dart | 6 +- 13 files changed, 127 insertions(+), 29 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 302c188e..0b556836 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -219,7 +219,7 @@ "unit": "الوحدة", "request": "الطلبات", "memberName": "اسم العضو", - "switchLogin": "عرض", + "switchLogin": "عرض الملف", "removeMember": "إزالة", "allowView": "السماح", "rejectView": "رفض", @@ -1349,7 +1349,7 @@ "monthlyHealthSummaryReportDisclaimer": "يعكس تقرير الملخص الصحي الشهري هذا المؤشرات الصحية ونتائج التحليل لأحدث الزيارات. يرجى ملاحظة أن هذا سيتم إرساله تلقائيًا من النظام ولا يعتبر تقريرًا رسميًا لذا لا ينبغي اتخاذ أي قرار طبي بناءً عليه", "updatingMonthlyReportStatus": "جاري تحديث حالة التقرير الشهري...", "monthlyReportStatusUpdatedSuccessfully": "تم تحديث حالة التقرير الشهري بنجاح", - "whoCanViewMyMedicalFile": "من يمكنه عرض ملفي الطبي؟", + "whoCanViewMyMedicalFile": "من يمكنه الاطلاع على ملفي الطبي", "acceptedYourRequestToBeYourFamilyMember": "{status} طلب الانضمام الى أفراد عائلتك", "canViewYourFile": "يمكنه عرض ملفك", "hasARequestPendingToBeYourFamilyMember": "لديه طلب {status} ليكون فردًا من عائلتك", @@ -1360,7 +1360,7 @@ "selectAProfile": "الرجاء تحديد ملف تعريف", "switchFamilyFile": "قم بالتبديل من قائمة الملفات الطبية أدناه", "medicalFiles": "الملفات الطبية", - "addANewFamilyMember": "إضافة فرد جديد", + "addANewFamilyMember": "اضافة", "viewInvoiceDetails": "عرض تفاصيل الفاتورة", "outPatient": "مريض خارجي", "invoiceDetails": "تفاصيل الفاتورة", @@ -1664,6 +1664,7 @@ "sortByLocation": "الترتيب حسب الموقع", "timeForFirstReminder": "اضافةتذكير", "reminderRemovalNote": "يمكنك إلغاء التذكير لاحقاً من التقويم.", + "prescriptionDatePassedError": "تاريخ الوصفة الطبية قد انتهى. لا يمكنك إضافة تذكير لهذه الوصفة.", "communicationLanguage": "لغة التواصل", "cmcServiceHeader": "فحص صحي شامل: تشخيص متقدم، معلومات صحية مفصلة", "cmcServiceDescription": "احصل على معلومات تفصيلية عن صحتك من خلال خدمات التشخيص المتقدمة لدينا. افهم جسمك بشكل أفضل لمستقبل صحي.", @@ -1702,7 +1703,7 @@ "doctorRemovedFromFavourite": "تمت إزالة من قائمة الأطباء المفضلين", "addNewFavorite": "إضافة مفضلة جديدة", "favourites": "المفضلة", - "pendingAncillaryOrders": "مدفوعات طلبات الاضافية", + "pendingAncillaryOrders": "طلبات غير مكتمله", "liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.", "containsImage": "يحتوي على صورة", "servicesSelected": "الخدمة / الخدمات", @@ -1810,5 +1811,10 @@ "watchUsOnYoutube": "شاهدنا على إنستغرام", "connectOnLinkedin": "تواصل معنا على لينكد إن", "riskFactorPageTitle": "اعراض تشعر بها", - "symptomsCheckSuggestions": "الاحتمالات" + "symptomsCheckSuggestions": "الاحتمالات", + "appointmentArrived": "تم الحضور", + "calendarPermissionAlertForAppointment": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير الموعد", +"rechargePageKey": "اختر المستفيد", + "selectFamilyFile": "اختيار ", + "ancillaryOrdersListNew": "طلبات غير مكتملة" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 3c9facad..029faa0d 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1652,6 +1652,7 @@ "calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.", "timeForFirstReminder": "Add Reminder", "reminderRemovalNote": "You can remove it from your calendar later by switching off the reminder", + "prescriptionDatePassedError": "Prescription date has already passed. You cannot add a reminder for this prescription.", "sortByLocation": "Sort by location", "sortByNearestLocation": "Sort by nearest to your location", "giveLocationPermissionForNearestList": "Please grant location permission from app settings to see the nearest locations.", @@ -1800,7 +1801,12 @@ "watchUsOnYoutube": "Watch us on Instagram", "connectOnLinkedin": "Connect on LinkedIn", "riskFactorPageTitle": "What do you feel?", - "symptomsCheckSuggestions": "Suggestions" + "symptomsCheckSuggestions": "Suggestions", +"appointmentArrived": "Arrived", + "calendarPermissionAlertForAppointment": "Please grant calendar access permission from app settings to set appointment reminder.", + "rechargePageKey": "Recharge Wallet", + "selectFamilyFile": "Select", + "ancillaryOrdersListNew": "Ancillary Orders List" } diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart index 59c0e170..d5e6b446 100644 --- a/lib/core/utils/calender_utils_new.dart +++ b/lib/core/utils/calender_utils_new.dart @@ -126,8 +126,7 @@ class CalenderUtilsNew { 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 startTimeOfDay = 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); @@ -139,28 +138,29 @@ class CalenderUtilsNew { // Validate that prescription is still active if (remainingDays <= 0) { - onFailure?.call("Prescription date has already passed. You cannot add a reminder for this prescription."); + onFailure?.call(LocaleKeys.prescriptionDatePassedError.tr()); return false; } bool statusOfOperation = false; for (int i = 0; i < remainingDays; i++) { - //event for number of days. + // Reset to the original time for each new day + DateTime actualDate = startTimeOfDay.add(Duration(days: i)); + + //event for number of days (frequency per day) for (int j = 0; j < frequencyNumber; j++) { statusOfOperation = await createOrUpdateEvent( - title: "$itemDescriptionN} Medication about to due for prescription , #$prescriptionNumber", + title: "$itemDescriptionN Medication about to due for prescription, #$prescriptionNumber", description: "$itemDescriptionN $frequencyNumber $route ", scheduleDateTime: actualDate, location: '', - //event id with varitions + //event id with variations reminderMinutes: reminderMinutes); if (!statusOfOperation) return false; actualDate = actualDate.add(Duration(hours: interval)); - // if (actualDate.difference(currentDay).inDays == 1) break; } - actualDate = actualDate.add(Duration(days: 1)); } return statusOfOperation; @@ -171,14 +171,17 @@ class CalenderUtilsNew { } FutureOr> getEvents() async { - var availableCalender = writableCalender.first; + var availableCalender = writableCalender.first; DateTime startEventsDate = Jiffy.parseFromDateTime(DateTime.now()).subtract(days: 30).dateTime; DateTime endEventsDate = Jiffy.parseFromDateTime(DateTime.now()).add(days: 120).dateTime; return await calender.listEvents(startEventsDate, endEventsDate, calendarIds: [availableCalender.id]); } FutureOr checkIfEventExist(String admissionId) async { - await ensureCalendarsLoaded(); + // await ensureCalendarsLoaded(); + if (writableCalender.isEmpty) { + await ensureCalendarsLoaded(); + } List events = await getEvents(); if (events.isEmpty) return false; for (var event in events) { diff --git a/lib/features/blood_donation/blood_donation_view_model.dart b/lib/features/blood_donation/blood_donation_view_model.dart index 7aa67d4f..3da82a75 100644 --- a/lib/features/blood_donation/blood_donation_view_model.dart +++ b/lib/features/blood_donation/blood_donation_view_model.dart @@ -231,30 +231,31 @@ class BloodDonationViewModel extends ChangeNotifier { } Future validateSelections() async { + AppState appState = getIt(); if (selectedCity == null) { await dialogService.showErrorBottomSheet( - message: "Please choose city", + message: appState.isArabic() ? "اختر المدينة" :"Please choose city", ); return false; } if (selectedGender == null) { await dialogService.showErrorBottomSheet( - message: "Please choose Gender", + message: appState.isArabic() ? "اختير الجنس" :"Please choose Gender", ); return false; } if (selectedBloodType == null) { await dialogService.showErrorBottomSheet( - message: "Please choose Blood Group", + message: appState.isArabic() ? "اختير فصيلة الدم" :"Please choose Blood Group", ); return false; } if (!isTermsAccepted) { await dialogService.showErrorBottomSheet( - message: "Please accept Terms and Conditions to continue", + message:appState.isArabic() ? "يرجى الموافقة على الشروط والأحكام للمتابعة" : "Please accept Terms and Conditions to continue", ); return false; } diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index ae3c7516..13811737 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -37,7 +37,7 @@ class AppointmentType { case 42: return LocaleKeys.confirmed.tr(); case 43: - return LocaleKeys.arrived.tr(); + return LocaleKeys.appointmentArrived.tr(); default: return LocaleKeys.booked.tr(); } diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index c374b3e2..577c68a1 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -74,7 +74,7 @@ class PrescriptionsViewModel extends ChangeNotifier { Future checkIfReminderExistForPrescription(int index) async { prescriptionDetailsList[index].hasReminder = await CalenderUtilsNew.instance.checkIfEventExist(prescriptionDetailsList[index].itemID?.toString() ?? ""); - notifyListeners(); + // notifyListeners(); return prescriptionDetailsList[index].hasReminder ?? false; } @@ -226,7 +226,11 @@ class PrescriptionsViewModel extends ChangeNotifier { } showError(String errorMessage) { - getIt().showErrorBottomSheet(message: errorMessage); + getIt().pop(); + Future.delayed(Duration(milliseconds: 500), (){ + getIt().showErrorBottomSheet(message: errorMessage); + }); + } Future submitPrescriptionDeliveryRequest( diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index a90fa9b5..9ed02e8a 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1658,6 +1658,7 @@ abstract class LocaleKeys { static const sortByLocation = 'sortByLocation'; static const timeForFirstReminder = 'timeForFirstReminder'; static const reminderRemovalNote = 'reminderRemovalNote'; + static const prescriptionDatePassedError = 'prescriptionDatePassedError'; static const communicationLanguage = 'communicationLanguage'; static const cmcServiceHeader = 'cmcServiceHeader'; static const cmcServiceDescription = 'cmcServiceDescription'; @@ -1803,5 +1804,10 @@ abstract class LocaleKeys { static const connectOnLinkedin = 'connectOnLinkedin'; static const riskFactorPageTitle = 'riskFactorPageTitle'; static const symptomsCheckSuggestions = 'symptomsCheckSuggestions'; + static const appointmentArrived = 'appointmentArrived'; + static const calendarPermissionAlertForAppointment = 'calendarPermissionAlertForAppointment'; + static const rechargePageKey = 'rechargePageKey'; + static const selectFamilyFile = 'selectFamilyFile'; + static const ancillaryOrdersListNew = 'ancillaryOrdersListNew'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 974c6019..87ca5e93 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -478,6 +478,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); }); }, + isForAppointment: true ); // If permission was not granted, revert the switch back to OFF diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index c671152f..538317bd 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -62,7 +62,7 @@ class _RechargeWalletPageState extends State { children: [ Expanded( child: CollapsingListView( - title: LocaleKeys.createAdvancedPayment.tr(context: context), + title: LocaleKeys.rechargePageKey.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(24.h), diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 9ad0fa31..55d503e2 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -632,7 +632,7 @@ class _ServicesPageState extends State { iconSize: 24.h, iconColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor, - text: LocaleKeys.addMember.tr(), + text: getIt.get().isArabic() ? LocaleKeys.add.tr() :LocaleKeys.addMember.tr(), borderWidth: 0.w, isBold: true, borderColor: Colors.transparent, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 6c1e74c2..86807150 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1219,7 +1219,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: LocaleKeys.ancillaryOrdersList.tr(context: context), + label: LocaleKeys.ancillaryOrdersListNew.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.ancillary_orders_list_icon, diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index 66ba13ed..e6f08496 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -1,3 +1,5 @@ +import 'dart:io' show Platform; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -8,12 +10,14 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/permission_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/scroll_wheel_time_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; class PrescriptionItemView extends StatelessWidget { int index; @@ -233,4 +237,71 @@ class PrescriptionItemView extends StatelessWidget { ], ); } + + /// Checks and requests calendar permissions for both Android and iOS + /// Returns true if all required permissions are granted + Future _checkAndRequestCalendarPermissions() async { + if (Platform.isAndroid) { + // Android: Check existing permission first using PermissionService + bool hasPermission = await PermissionService.isCalendarPermissionEnabled(); + if (hasPermission) { + return true; + } + + // Request calendarFullAccess permission (Android 14+ requires this) + // This internally requests both READ_CALENDAR and WRITE_CALENDAR + PermissionStatus status = await Permission.calendarFullAccess.request(); + + // If calendarFullAccess request didn't show dialog or failed, + // fallback to requesting individual permissions + if (!status.isGranted) { + Map statuses = await [ + Permission.calendarWriteOnly, + ].request(); + + return statuses[Permission.calendarWriteOnly]?.isGranted ?? false; + } + + return status.isGranted; + } else { + // iOS: Check if we already have full access + PermissionStatus fullAccessStatus = await Permission.calendarFullAccess.status; + + if (fullAccessStatus.isGranted) { + return true; + } + + // Request write permission first (required before full access on iOS) + PermissionStatus writeStatus = await Permission.calendarWriteOnly.request(); + if (!writeStatus.isGranted) { + return false; + } + + // Then request full access permission + PermissionStatus fullStatus = await Permission.calendarFullAccess.request(); + return fullStatus.isGranted; + } + } + + /// Shows permission denied dialog with option to open settings + Future _showPermissionDeniedDialog(BuildContext context) async { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + Navigator.of(context).pop(); + }, + onConfirmTap: () async { + Navigator.of(context).pop(); + await openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 069022d1..f7145976 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -18,7 +18,7 @@ import 'package:permission_handler/permission_handler.dart'; class BottomSheetUtils { Future 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 { + {required Function() onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool isMultiAllowed = false, bool isForAppointment = false}) async { // Check and request permissions based on platform bool hasPermission = await _checkAndRequestCalendarPermissions(); @@ -79,7 +79,7 @@ class BottomSheetUtils { } /// Shows permission denied dialog with option to open settings - Future _showPermissionDeniedDialog() async { + Future _showPermissionDeniedDialog({bool isForAppointment = false}) async { BuildContext? context = GetIt.instance().navigatorKey.currentContext; if (context == null) return; @@ -87,7 +87,7 @@ class BottomSheetUtils { context, title: LocaleKeys.notice.tr(context: context), child: Utils.getWarningWidget( - loadingText: LocaleKeys.calendarPermissionAlert.tr(), + loadingText: isForAppointment ? LocaleKeys.calendarPermissionAlertForAppointment.tr() : LocaleKeys.calendarPermissionAlert.tr(), isShowActionButtons: true, onCancelTap: () { GetIt.instance().pop();