diff --git a/assets/images/svg/invoice_list_icon.svg b/assets/images/svg/invoice_list_icon.svg new file mode 100644 index 00000000..6b9e698f --- /dev/null +++ b/assets/images/svg/invoice_list_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index a1d22707..b0d67521 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -694,7 +694,7 @@ "retouch": "روتوش", "bikini": "بيكيني", "totalMinutes": "إجمالي الدقائق", - "feedback": "ملاحظات", + "feedback": "رأيك يهمنا", "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", "emptySubject": "يرجى إدخال الموضوع", "emptyMessage": "يرجى إدخال الرسالة", @@ -702,10 +702,10 @@ "complainAppo": "شكوى بخصوص الموعد", "complainAppoAlt": "شكوى بدون موعد", "messageType": "نوع الرسالة", - "feedbackType": "نوع الملاحظات", + "feedbackType": "نوع الرسالة", "compliment": "تقدير", "suggestion": "اقتراح", - "yourFeedback": "تم إرسال ملاحظاتك", + "yourFeedback": "تم إرسال رأيك", "selectPart": "يرجى اختيار الجزء الذي تشتكي منه", "number": "رقم", "notClassified": "غير مصنف", @@ -1705,5 +1705,8 @@ "takePhotoWithCamera": "التقط صورة بالكاميرا", "selectFromPhotoLibrary": "اختر من مكتبة الصور", "selectDocumentFiles": "اختر ملفات المستندات", - "profilePicture": "صورة الملف الشخصي" + "profilePicture": "صورة الملف الشخصي", + "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", + "sendingAskDrRequest": "جارٍ إرسال الطلب...", + "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 3b1070d9..38ce9662 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1697,5 +1697,8 @@ "takePhotoWithCamera": "Take a photo with your camera", "selectFromPhotoLibrary": "Select from your photo library", "selectDocumentFiles": "Select document files", - "profilePicture": "Profile Picture" + "profilePicture": "Profile Picture", + "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "sendingAskDrRequest": "Sending Request...", + "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon." } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index c74d9fd5..2b5abbe9 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -25,6 +25,7 @@ class ApiConsts { static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + // static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; @@ -44,7 +45,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; break; case AppEnvironmentTypeEnum.dev: @@ -80,7 +81,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; break; case AppEnvironmentTypeEnum.qa: @@ -475,7 +476,8 @@ var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointm var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime"; //URL to dental doctors list -var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping"; +// var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping"; +var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping_Nearest"; //URL to get doctor free slots var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 14227fa6..5adc0cd5 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -357,6 +357,7 @@ class AppAssets { static const String biometricLockIcon = '$svgBasePath/biometric_lock_icon.svg'; static const String imageIcon = '$svgBasePath/image_icon.svg'; static const String appointmentRatingIcon = '$svgBasePath/appointment_rating_icon.svg'; + static const String invoiceListIcon = '$svgBasePath/invoice_list_icon.svg'; static const String open_camera = '$svgBasePath/open_camera.svg'; static const String gallery = '$svgBasePath/gallery.svg'; static const String files = '$svgBasePath/files.svg'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 5bbf19aa..1b4a5e4d 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -10,14 +10,19 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import '../features/authentication/models/request_models/registration_payload_model.dart'; class AppState { NavigationService navigationService; + CacheService cacheService; - AppState({required this.navigationService}){ + static const String _profileImageKey = 'profile_image_data'; + + AppState({required this.navigationService, required this.cacheService}){ checkIfGMSAvailableOrNot(); + _loadProfileImageFromCache(); } double userLat = 0.0; @@ -88,7 +93,30 @@ class AppState { String? get getProfileImageData => _profileImageData; - set setProfileImageData(String? value) => _profileImageData = value; + set setProfileImageData(String? value) { + print('📸 AppState: Setting profile image data - ${value != null && value.isNotEmpty ? "Has data (${value.length} chars)" : "Empty/null"}'); + _profileImageData = value; + // Persist to cache + if (value != null && value.isNotEmpty) { + cacheService.saveString(key: _profileImageKey, value: value); + print('💾 AppState: Profile image saved to cache'); + } else { + cacheService.remove(key: _profileImageKey); + print('🗑️ AppState: Profile image removed from cache'); + } + } + + /// Load profile image from cache on app initialization + void _loadProfileImageFromCache() { + _profileImageData = cacheService.getString(key: _profileImageKey); + print('📂 AppState: Loaded profile image from cache - ${_profileImageData != null && _profileImageData!.isNotEmpty ? "Has data (${_profileImageData!.length} chars)" : "Empty/null"}'); + } + + /// Clear profile image from cache (e.g., on logout) + void clearProfileImageCache() { + _profileImageData = null; + cacheService.remove(key: _profileImageKey); + } SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement; @@ -255,4 +283,18 @@ class AppState { _userSwitchPayloads.clear(); _childSwitchCounter = 0; } + + void clearChildUsersKeepSuperUser() { + // Keep the super user payload + final superUserPayload = _userSwitchPayloads['superUser']; + + // Clear all payloads + _userSwitchPayloads.clear(); + _childSwitchCounter = 0; + + // Restore super user if it existed + if (superUserPayload != null) { + _userSwitchPayloads['superUser'] = superUserPayload; + } + } } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 657d03ea..3b7253db 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -113,7 +113,12 @@ class AppDependencies { getIt.registerLazySingleton(() => LocalAuthentication()); getIt.registerLazySingleton(() => GAnalytics()); - getIt.registerLazySingleton(() => AppState(navigationService: getIt())); + + // Register CacheService before AppState so it can be injected + final sharedPreferences = await SharedPreferences.getInstance(); + getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); + + getIt.registerLazySingleton(() => AppState(navigationService: getIt(), cacheService: getIt())); getIt.registerLazySingleton(() => LocationUtils( isShowConfirmDialog: false, navigationService: getIt(), @@ -126,8 +131,6 @@ class AppDependencies { navigationService: getIt(), )); - final sharedPreferences = await SharedPreferences.getInstance(); - getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); getIt.registerLazySingleton(() => NotificationServiceImp( diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 39d8e3ee..c355819e 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -314,7 +314,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { requestBody['PatientIdentificationID'] = ""; requestBody.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ForRegisteration'].contains(key)); appState.setIsChildLoggedIn = false; - appState.clearUserSwitchPayloads(); + appState.clearChildUsersKeepSuperUser(); } } else { appState.setIsChildLoggedIn = true; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 2e9426a0..d94b100b 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -631,6 +631,10 @@ class AuthenticationViewModel extends ChangeNotifier { } else { if (activation.list != null && activation.list!.isNotEmpty) { if (isSwitchUser) { + // Clear profile image cache when switching users + _appState.clearProfileImageCache(); + log("🔄 Switching user - cleared profile image cache"); + if (_appState.getIsChildLoggedIn) { _appState.setSuperUserID = null; // _appState.setIsChildLoggedIn = false; @@ -642,17 +646,20 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); } } else { - _appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId; - // _appState.setIsChildLoggedIn = false; + var allUsersPayload = _appState.getUserSwitchPayloads; + _appState.setSuperUserID = allUsersPayload['superUser']?['PatientID']; activation.list!.first.isParentUser = true; _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); } // Fetch profile image after switching user - _fetchProfileImage(activation.list!.first.patientId); + await _fetchProfileImage(activation.list!.first.patientId); } else { activation.list!.first.isParentUser = true; _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); + + // Fetch profile image after login + await _fetchProfileImage(activation.list!.first.patientId); } activation.list!.first.bloodGroup = activation.patientBloodType; activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode; @@ -660,8 +667,6 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; - // Fetch profile image after login - _fetchProfileImage(activation.list!.first.patientId); // Fetch patient blood type from new API // await getPatientBloodTypeNew(); @@ -1179,6 +1184,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setIsAuthenticated = false; _appState.setAppAuthToken = ""; _appState.setUserBloodGroup = ""; + _appState.clearProfileImageCache(); // Clear profile image cache _appState.setSuperUserID = null; _appState.setIsChildLoggedIn = false; _appState.setFamilyFileTokenID = ""; @@ -1227,13 +1233,8 @@ class AuthenticationViewModel extends ChangeNotifier { Future _fetchProfileImage(int? patientID) async { if (patientID == null) return; - // Skip if already loaded in AppState - if (_appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) { - log("Profile image already cached, skipping API call"); - return; - } - try { + log("Fetching profile image for patient ID: $patientID"); final profileSettingsRepo = GetIt.instance(); final result = await profileSettingsRepo.getProfileImage(patientID: patientID); @@ -1246,17 +1247,23 @@ class AuthenticationViewModel extends ChangeNotifier { if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) { var imageList = apiResponse.data['Patient_GetProfileImageDataList']; if (imageList is List && imageList.isNotEmpty) { - String? imageData = imageList[0]['PatientsProfileImageData']; + String? imageData = imageList[0]['ImageData']; if (imageData != null && imageData.isNotEmpty) { _appState.setProfileImageData = imageData; - log("Profile image loaded successfully"); + log("✅ Profile image loaded and cached successfully"); + } else { + log("⚠️ Profile image data is empty"); } + } else { + log("⚠️ Profile image list is empty"); } + } else { + log("⚠️ No profile image data in response"); } }, ); } catch (e) { - log("Error fetching profile image: $e"); + log("❌ Error fetching profile image: $e"); } } } diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 7a984399..0db37274 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -86,7 +86,7 @@ abstract class BookAppointmentsRepo { {required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}); Future>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, - {Function(dynamic)? onSuccess, Function(String)? onError}); + {bool isGetNearestAppointments, Function(dynamic)? onSuccess, Function(String)? onError}); Future>>> getLaserClinics(int laserCategoryID, int projectID, int languageID, {Function(dynamic)? onSuccess, Function(String)? onError}); @@ -877,12 +877,13 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { @override Future>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, - {Function(dynamic)? onSuccess, Function(String)? onError}) async { + {bool isGetNearestAppointments = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { Map mapDevice = { "ProjectID": projectID, "ChiefComplaintID": chiefComplaintID, "isDentalAllowedBackend": true, "IsPublicRequest": true, + "IsGetNearAppointment": isGetNearestAppointments }; try { diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 647b8632..7ac6ae45 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -1471,12 +1471,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async { - setIsNearestAppointmentSelected(false); doctorsList.clear(); notifyListeners(); projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; - final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID); + final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID, isGetNearestAppointments: isNearestAppointmentSelected); result.fold( (failure) async { diff --git a/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart index c36b9ec5..8ad174ff 100644 --- a/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart @@ -6,6 +6,7 @@ class PatientQueueDetails { String? roomNo; String? calledOn; bool? servingNow; + int? isClinicConfigured; PatientQueueDetails( {this.patientID, @@ -14,7 +15,7 @@ class PatientQueueDetails { this.callType, this.roomNo, this.calledOn, - this.servingNow}); + this.servingNow, this.isClinicConfigured}); PatientQueueDetails.fromJson(Map json) { patientID = json['patientID']; @@ -24,10 +25,11 @@ class PatientQueueDetails { roomNo = json['roomNo']; calledOn = json['calledOn']; servingNow = json['servingNow']; + isClinicConfigured = json['isClinicConfigured']; } Map toJson() { - final Map data = new Map(); + final Map data = Map(); data['patientID'] = this.patientID; data['patientName'] = this.patientName; data['queueNo'] = this.queueNo; @@ -35,6 +37,7 @@ class PatientQueueDetails { data['roomNo'] = this.roomNo; data['calledOn'] = this.calledOn; data['servingNow'] = this.servingNow; + data['isClinicConfigured'] = this.isClinicConfigured; return data; } } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 45c28c45..7344bc22 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -77,7 +77,7 @@ abstract class MyAppointmentsRepo { Future>> sendDoctorRate( int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName); - Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}); + Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}); Future>> convertPatientToCash({required int projectID}); @@ -833,8 +833,8 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { } @override - Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}) async { - Map mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"}; + Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}) async { + Map mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "clinicID": clinicID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"}; try { GenericApiModel>? apiResponse; Failure? failure; @@ -842,6 +842,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { + // final list = response['data']; final list = response['data']; final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index bc0bfc74..a6f306ba 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -929,9 +929,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { //TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State; isPatientHasQueueAppointment = false; isAppointmentQueueDetailsLoading = true; + patientQueueDetailsList.clear(); notifyListeners(); final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails( + clinicID: patientArrivedAppointmentsHistoryList.first.clinicID, appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); isAppointmentQueueDetailsLoading = false; @@ -948,8 +950,32 @@ class MyAppointmentsViewModel extends ChangeNotifier { onError!(apiResponse.errorMessage!); } else if (apiResponse.messageStatus == 1) { if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { - isPatientHasQueueAppointment = true; patientQueueDetailsList = apiResponse.data!; + + if (patientQueueDetailsList.first.isClinicConfigured == 1) { + isPatientHasQueueAppointment = true; + for (var element in patientQueueDetailsList) { + if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { + currentPatientQueueDetails = element; + currentQueueStatus = element.callType!; + // currentQueueStatus = 2; + break; + } + } + // patientQueueDetailsList.first.callType = 1; + patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + } else { + isPatientHasQueueAppointment = false; + } + } else { + isPatientHasQueueAppointment = false; + } + + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + patientQueueDetailsList = apiResponse.data!; + + // if (patientQueueDetailsList.first.isClinicConfigured == 1) { + isPatientHasQueueAppointment = true; for (var element in patientQueueDetailsList) { if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { currentPatientQueueDetails = element; @@ -959,8 +985,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } // patientQueueDetailsList.first.callType = 1; - patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + // } else { + // isPatientHasQueueAppointment = false; + // } } else { isPatientHasQueueAppointment = false; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index cd917683..2c0d5194 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1697,5 +1697,7 @@ abstract class LocaleKeys { static const selectFromPhotoLibrary = 'selectFromPhotoLibrary'; static const selectDocumentFiles = 'selectDocumentFiles'; static const profilePicture = 'profilePicture'; + static const sendingAskDrRequest = 'sendingAskDrRequest'; + static const askDrRequestSentSuccess = 'askDrRequestSentSuccess'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 31cd5403..c668ebff 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -912,9 +912,9 @@ class _AppointmentDetailsPageState extends State { ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? - (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || - widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || + ? (!(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) || + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) ? SizedBox.shrink() : CustomButton( diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index fb24f689..0542f76d 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -243,88 +243,88 @@ class AppointmentCard extends StatelessWidget { (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { - if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) && - DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) { - return Row( - children: [ - Expanded( - flex: 6, - child: CustomButton( - text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); - await myAppointmentsViewModel.isDoctorAvailable( - projectID: patientAppointmentHistoryResponseModel.projectID, - doctorId: patientAppointmentHistoryResponseModel.doctorID, - clinicId: patientAppointmentHistoryResponseModel.clinicID, - onSuccess: (value) async { - if (value) { - await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.askDoctor.tr(context: context), - child: AskDoctorRequestTypeSelect( - askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - print("Doctor is not available"); - } - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: (isFoldable || isTablet) ? 50.h : 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.h, - side: BorderSide( - color: AppColors.textColor, - width: 1.2, - ), - ), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, - width: 24.w, - height: 24.h, - fit: BoxFit.contain, - ), - ), - ).toShimmer2(isShow: isLoading).onPress(() { - _goToDetails(context); - }), - ), - ], - ); - } else { - return CustomButton( - text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), + // if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) && + // DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) { + // return Row( + // children: [ + // Expanded( + // flex: 6, + // child: CustomButton( + // text: LocaleKeys.askDoctor.tr(context: context), + // onPressed: () async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + // await myAppointmentsViewModel.isDoctorAvailable( + // projectID: patientAppointmentHistoryResponseModel.projectID, + // doctorId: patientAppointmentHistoryResponseModel.doctorID, + // clinicId: patientAppointmentHistoryResponseModel.clinicID, + // onSuccess: (value) async { + // if (value) { + // await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.askDoctor.tr(context: context), + // child: AskDoctorRequestTypeSelect( + // askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + // myAppointmentsViewModel: myAppointmentsViewModel, + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // ), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // } else { + // print("Doctor is not available"); + // } + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // // height: isTablet || isFoldable ? 46.h : 40.h, + // height: 40.h, + // icon: AppAssets.ask_doctor_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 8.h), + // Expanded( + // flex: 1, + // child: Container( + // height: (isFoldable || isTablet) ? 50.h : 40.h, + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.textColor, + // borderRadius: 10.h, + // side: BorderSide( + // color: AppColors.textColor, + // width: 1.2, + // ), + // ), + // child: Transform.flip( + // flipX: appState.isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon, + // iconColor: AppColors.whiteColor, + // width: 24.w, + // height: 24.h, + // fit: BoxFit.contain, + // ), + // ), + // ).toShimmer2(isShow: isLoading).onPress(() { + // _goToDetails(context); + // }), + // ), + // ], + // ); + // } else { + return CustomButton( + text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), onPressed: () { if (isFromMedicalReport) { if (isForFeedback) { @@ -359,7 +359,7 @@ class AppointmentCard extends StatelessWidget { iconColor: AppColors.primaryRedColor, iconSize: 16.h, ); - } + // } } else { if (isFromMedicalReport) { if (isForEyeMeasurements) { @@ -390,8 +390,9 @@ class AppointmentCard extends StatelessWidget { ); } } - return Row( - children: [ + return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) + ? Row( + children: [ Expanded( flex: 6, child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) @@ -441,58 +442,85 @@ class AppointmentCard extends StatelessWidget { }), ), ], - ); + ) + : CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); } } Widget _getArrivedButton(BuildContext context) { - final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; - - if (isRecent) { - return CustomButton( - text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); - await myAppointmentsViewModel.isDoctorAvailable( - projectID: patientAppointmentHistoryResponseModel.projectID, - doctorId: patientAppointmentHistoryResponseModel.doctorID, - clinicId: patientAppointmentHistoryResponseModel.clinicID, - onSuccess: (value) async { - if (value) { - await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.askDoctor.tr(context: context), - child: AskDoctorRequestTypeSelect( - askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - print("Doctor is not available"); - } - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); - } + // final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; + // + // if (isRecent) { + // return CustomButton( + // text: LocaleKeys.askDoctor.tr(context: context), + // onPressed: () async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + // await myAppointmentsViewModel.isDoctorAvailable( + // projectID: patientAppointmentHistoryResponseModel.projectID, + // doctorId: patientAppointmentHistoryResponseModel.doctorID, + // clinicId: patientAppointmentHistoryResponseModel.clinicID, + // onSuccess: (value) async { + // if (value) { + // await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.askDoctor.tr(context: context), + // child: AskDoctorRequestTypeSelect( + // askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + // myAppointmentsViewModel: myAppointmentsViewModel, + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // ), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // } else { + // print("Doctor is not available"); + // } + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // // height: isTablet || isFoldable ? 46.h : 40.h, + // height: 40.h, + // icon: AppAssets.ask_doctor_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ); + // } return CustomButton( borderSide: BorderSide( diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 5de06455..4b5df07b 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -126,7 +126,7 @@ class AppointmentDoctorCard extends StatelessWidget { DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false, )}" - .toText10(isEnglishOnly: true), + .toText10(isBold: true), ), ), AppCustomChipWidget( @@ -142,8 +142,10 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { - DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? 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; @@ -265,7 +267,7 @@ class AppointmentDoctorCard extends StatelessWidget { fontSize: 14.f, isBold: true, borderRadius: 12.r, - height: 40.h, + height: 46.h, icon: AppAssets.appointment_calendar_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, @@ -284,7 +286,7 @@ class AppointmentDoctorCard extends StatelessWidget { fontSize: 14.f, isBold: true, borderRadius: 12.r, - height: 40.h, + height: 46.h, icon: AppAssets.cancel, iconColor: AppColors.whiteColor, iconSize: 16.h, diff --git a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart index 35b15399..bf74863a 100644 --- a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart +++ b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ 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/services/navigation_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/common_bottom_sheet.dart'; @@ -95,8 +97,8 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { child: CustomButton( text: LocaleKeys.confirm.tr(), onPressed: () async { - Navigator.pop(context); - LoaderBottomSheet.showLoader(loadingText: "Sending Request..."); + Navigator.of(context).pop(); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingAskDrRequest.tr(context: context)); await myAppointmentsViewModel.sendAskDocCallRequest( patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, requestType: selectedParameterCodeValue.toString(), @@ -104,17 +106,10 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!, onSuccess: (val) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon."), - callBackFunc: () { - Navigator.of(context).pop(); - }, - title: "", - isCloseButtonVisible: true, - isDismissible: false, - isFullScreen: false, - ); + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: Padding(padding: EdgeInsets.symmetric(horizontal: 24.h), child: Utils.getSuccessWidget(loadingText: LocaleKeys.askDrRequestSentSuccess.tr())), callBackFunc: () { + Navigator.of(context).pop(); + }, title: "", isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, isAutoDismiss: true); }, onError: (errMessage) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 4659972e..6d326939 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -92,7 +92,7 @@ class _SavedLogin extends State { Container( padding: EdgeInsets.all(16.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), ]), child: Column( @@ -288,6 +288,7 @@ class _SavedLogin extends State { child: CustomButton( text: LocaleKeys.guest.tr(), onPressed: () { + authVm.setIsInitialLoginNavigated(true); Navigator.pushAndRemoveUntil( context, CustomPageRoute( diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index bbc88214..47c4f968 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -355,10 +355,10 @@ class _BookAppointmentPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h), - SizedBox( - width: 80.w, - child: LocaleKeys.add.tr(context: context).toText12( + Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h, applyThemeColor: false), + SizedBox( + width: 80.w, + child: LocaleKeys.add.tr(context: context).toText12( color: AppColors.textColor, isBold: true, isCenter: true, diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index de2e2ae8..d9012573 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -220,8 +220,9 @@ class _SelectDoctorPageState extends State { ], ).paddingSymmetrical(0.h, 0.h), if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) SizedBox(height: 16.h), - bookAppointmentsVM.doctorsListGrouped.isNotEmpty - ? Row( + // bookAppointmentsVM.doctorsListGrouped.isNotEmpty + // ? + Row( mainAxisSize: MainAxisSize.max, children: [ Column( @@ -243,8 +244,8 @@ class _SelectDoctorPageState extends State { }, ), ], - ) - : SizedBox.shrink(), + ), + // : SizedBox.shrink(), ListView.separated( padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 11042a57..7b9ce848 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -159,10 +159,12 @@ class DoctorCard extends StatelessWidget { ], ), ), - Expanded( - flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), - ), + (doctorsListResponseModel.isLiveCare ?? true) + ? SizedBox.shrink() + : Expanded( + flex: 1, + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), + ), ], ), SizedBox(height: 16.h), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 33b12855..00142c23 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -142,6 +142,16 @@ class LandingPageData { textColor: AppColors.blackColor, isBold: false, ), + ServiceCardData( + serviceName: "my_invoice", + icon: AppAssets.invoiceListIcon, + title: LocaleKeys.invoiceList, + subtitle: LocaleKeys.myDoctorSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), ]; static List get getServiceCardsList => [ diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 298bca81..c89727fa 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -105,9 +105,13 @@ class _LandingPageState extends State { final ScrollController _horizontalScrollController = ScrollController(); + final ScrollController _scrollController = ScrollController(); + double _scrollOpacity = 0.0; + @override void dispose() { _horizontalScrollController.dispose(); + _scrollController.dispose(); super.dispose(); } @@ -123,6 +127,17 @@ class _LandingPageState extends State { showQuickLogin(context); }); } + + _scrollController.addListener(() { + final scrollOffset = _scrollController.offset; + final newOpacity = (scrollOffset / 50).clamp(0.0, 1.0); + if (_scrollOpacity != newOpacity) { + setState(() { + _scrollOpacity = newOpacity; + }); + } + }); + scheduleMicrotask(() async { if (!appState.isAuthenticated) { // LoaderBottomSheet.showLoader(); @@ -189,7 +204,7 @@ class _LandingPageState extends State { // Refresh Appointments Data myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + myAppointmentsViewModel.getPatientAppointments(true, true); // Refresh Appointments Data habibWalletVM.initHabibWalletProvider(); @@ -204,6 +219,7 @@ class _LandingPageState extends State { } }, child: SingleChildScrollView( + controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) @@ -242,6 +258,7 @@ class _LandingPageState extends State { width: 42.w, height: 42.h, fit: BoxFit.cover, + isCircular: true, ), ).expanded : CustomButton( @@ -697,8 +714,8 @@ class _LandingPageState extends State { itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( + duration: const Duration(milliseconds: 500), + child: SlideAnimation( horizontalOffset: 100.0, child: FadeInAnimation( child: SmallServiceCard( @@ -829,6 +846,25 @@ class _LandingPageState extends State { ), ), ), + AnimatedOpacity( + opacity: _scrollOpacity, + duration: Duration(milliseconds: 200), + child: Container( + height: MediaQuery.paddingOf(context).top + 10.h, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.scaffoldBgColor, + AppColors.scaffoldBgColor.withValues(alpha: 0.9), + AppColors.scaffoldBgColor.withValues(alpha: 0.0), + ], + stops: [0.0, 0.7, 1.0], + ), + ), + ), + ), (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) ? Container( height: MediaQuery.paddingOf(context).top + 50.h, @@ -1038,7 +1074,7 @@ class _LandingPageState extends State { // Queue Card: Currently serving section Widget _buildServingNowSection() { if (myAppointmentsViewModel.patientQueueDetailsList.isEmpty) { - return SizedBox(height: 12.h); + return SizedBox(height: 24.h); } final servingQueue = myAppointmentsViewModel.patientQueueDetailsList.first; diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index ff834204..45023c3b 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -92,6 +93,13 @@ class SmallServiceCard extends StatelessWidget { ), ); break; + case "my_invoice": + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesList(), + ), + ); + break; case "prescriptions": Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart index 6fb0f393..4debd41b 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart @@ -351,7 +351,7 @@ class _LabAiAnalysisDetailedPageState extends State { if (priority == null) return AppColors.primaryRedColor; switch (priority.toLowerCase()) { case 'high': - return Color(0xFFED1C2B).withOpacity(.10); + return AppColors.secondaryLightRedColor; case 'medium' || 'moderate': return AppColors.lightGreenButtonColor; case 'low': diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 76c0d083..7bbfc936 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -247,6 +247,7 @@ class _MedicalFilePageState extends State { width: 56.w, height: 56.h, fit: BoxFit.cover, + isCircular: true, ), SizedBox(width: 8.w), Column( diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 1938daba..b3081dc1 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -181,6 +181,7 @@ class _FamilyCardsState extends State { width: 56.w, height: 56.h, fit: BoxFit.cover, + isCircular: true, ), SizedBox(width: 8.w), Column( @@ -199,7 +200,7 @@ class _FamilyCardsState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isBold: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -319,7 +320,6 @@ class _FamilyCardsState extends State { final profile = widget.profileViewList![index]; final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; - print(jsonEncode(appState.getUserSwitchPayloads)); final isSuperUser = appState.getUserSwitchPayloads; // TODO: canSwitch functionality is currently disabled - set to true to allow all switches // Original logic: final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); @@ -340,6 +340,8 @@ class _FamilyCardsState extends State { gender: profile.gender, age: profile.age, fit: BoxFit.cover, + customProfileImageData: "", // Prevent showing logged-in user's image for family members + isCircular: true, ), SizedBox(height: 8.h), (profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1), diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index a935f0d1..5a8e9c70 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import '../../widgets/buttons/custom_button.dart'; @@ -215,23 +216,36 @@ class _MyInvoicesListState extends State { child: InvoiceListCard( getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], onTap: () async { - myInvoicesVM.setInvoiceDetailLoading(); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); - await myInvoicesVM.getInvoiceDetails( - appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); + await myInvoicesViewModel.sendInvoiceEmail( + appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectId!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myInvoicesViewModel.downloadInvoicePDF( + setupId: myInvoicesVM.allInvoicesList[index].setupId!, invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, projectID: myInvoicesVM.allInvoicesList[index].projectId!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: MyInvoicesDetailsPage( - getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, - getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], - ), - ), - ); - }, onError: (err) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( @@ -241,8 +255,56 @@ class _MyInvoicesListState extends State { isFullScreen: false, isCloseButtonVisible: true, ); - }); + }, + onSuccess: (value) async { + LoaderBottomSheet.hideLoader(); + if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }, + ); + } }, + // onTap: () async { + // myInvoicesVM.setInvoiceDetailLoading(); + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); + // await myInvoicesVM.getInvoiceDetails( + // appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + // invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + // projectID: myInvoicesVM.allInvoicesList[index].projectId!, + // onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: MyInvoicesDetailsPage( + // getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, + // getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + // ), + // ), + // ); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // }, ), ), ), diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index 3aacb85c..42d2fd92 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -140,7 +140,8 @@ class InvoiceListCard extends StatelessWidget { ), SizedBox(height: 16.h), CustomButton( - text: LocaleKeys.viewInvoiceDetails.tr(context: context), + // text: LocaleKeys.viewInvoiceDetails.tr(context: context), + text: Utils.isVidaPlusProject(getInvoicesListResponseModel.projectId ?? 0) ? LocaleKeys.sendEmail.tr(context: context) : LocaleKeys.downloadInvoice.tr(context: context), onPressed: () { if (onTap != null) { onTap!(); diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 0abcfd81..377014ac 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -459,6 +459,7 @@ class FamilyCardWidget extends StatelessWidget { gender: profile.gender, age: profile.age, fit: BoxFit.cover, + isCircular: true, ), Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/profile_settings/widgets/family_card_widget.dart b/lib/presentation/profile_settings/widgets/family_card_widget.dart index 97039de1..768fe538 100644 --- a/lib/presentation/profile_settings/widgets/family_card_widget.dart +++ b/lib/presentation/profile_settings/widgets/family_card_widget.dart @@ -56,6 +56,8 @@ class FamilyCardWidget extends StatelessWidget { gender: profile.gender, age: profile.age, fit: BoxFit.cover, + customProfileImageData: "", + isCircular: true,// Prevent showing logged-in user's image for family members ), Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart index 5b6fe7f9..4cecc651 100644 --- a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart +++ b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart @@ -47,9 +47,9 @@ class _PreferredLanguageWidgetState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: Column( children: [ - languageItem("English", "2"), - 1.divider, languageItem("العربية", "1"), + 1.divider, + languageItem("English", "2"), ], ), ), @@ -104,7 +104,7 @@ class _PreferredLanguageWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'GESSTwo' : 'Poppins').expanded, + title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'CairoArabic' : 'Poppins').expanded, ], ).paddingOnly(left: 16, right: 16).onPress(() { setState(() { diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index a6769b3e..f041cd85 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -103,7 +103,7 @@ class _AppLanguageChangeState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'GESSTwo' : 'Poppins').expanded, + title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'CairoArabic' : 'Poppins').expanded, ], ).paddingOnly(left: 16, right: 16).onPress(() { setState(() { diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 85d50b48..d47971d7 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -238,7 +238,7 @@ void showCommonBottomSheetWithoutHeight( duration: Duration(milliseconds: 500), reverseDuration: Duration(milliseconds: 300), ), - constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width //MediaQuery.of(context).size.width, // Full width + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width //MediaQuery.of(context).size.width, // Full width ), context: context, isScrollControlled: true, diff --git a/lib/widgets/user_avatar_widget.dart b/lib/widgets/user_avatar_widget.dart index 6a17160a..56545a0d 100644 --- a/lib/widgets/user_avatar_widget.dart +++ b/lib/widgets/user_avatar_widget.dart @@ -36,6 +36,12 @@ class UserAvatarWidget extends StatelessWidget { // Use custom profile image data if provided, otherwise use AppState data final profileImageData = customProfileImageData ?? appState.getProfileImageData; + // Debug logging + print('🖼️ UserAvatarWidget build - Has profile image: ${profileImageData != null && profileImageData.isNotEmpty}'); + if (profileImageData != null && profileImageData.isNotEmpty) { + print('🖼️ Profile image data length: ${profileImageData.length} characters'); + } + // Determine the default image based on gender and age final String defaultImage; if (userGender == 1) {