diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2987d3b..5d74c4d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -160,6 +160,7 @@ dependencies { annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") implementation("com.mapbox.maps:android:11.5.0") + implementation("com.mapbox.mapboxsdk:mapbox-sdk-turf:7.3.1") // implementation("com.mapbox.maps:android:11.4.0") // AARs diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt index b822d67..70889d3 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt @@ -21,7 +21,7 @@ class PenguinNavigator() { val postToken = PostToken(clientID, clientKey) getToken(mContext, postToken, object : RefIdDelegate { override fun onRefByIDSuccess(PoiId: String?) { - Log.e("navigateTo", "PoiId is+++++++ $PoiId") + Log.e("navigateTo", "PoiId is+++++++ $refID") PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate { override fun onRefByIDSuccess(PoiId: String?) { diff --git a/assets/images/svg/back_top_nav_icon.svg b/assets/images/svg/back_top_nav_icon.svg new file mode 100644 index 0000000..62bf96b --- /dev/null +++ b/assets/images/svg/back_top_nav_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/forward_top_nav_icon.svg b/assets/images/svg/forward_top_nav_icon.svg new file mode 100644 index 0000000..089161c --- /dev/null +++ b/assets/images/svg/forward_top_nav_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios/Runner/Penguin/PenguinNavigator.swift b/ios/Runner/Penguin/PenguinNavigator.swift index 31cf626..5b9934b 100644 --- a/ios/Runner/Penguin/PenguinNavigator.swift +++ b/ios/Runner/Penguin/PenguinNavigator.swift @@ -14,7 +14,7 @@ class PenguinNavigator { } func navigateToPOI( referenceId:String,completion: @escaping (Bool, String?) -> Void) { - PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: true) { [weak self] token, error in + PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: false) { [weak self] token, error in if let error = error { let errorMessage = "Token error while getting the for Navigate to method" diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift index d5303e2..508fb74 100644 --- a/ios/Runner/Penguin/PenguinView.swift +++ b/ios/Runner/Penguin/PenguinView.swift @@ -312,6 +312,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia print("====== after eventsDelegate onPenNavSuccess =========") + + PenNavUIManager.shared.navigate(to: "108") @@ -385,21 +387,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia - self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in - - if success { - - print("Navigation successful") - - } else { - - print("Navigation failed: \(errorMessage ?? "Unknown error")") - - } - - - - } +// self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in +// +// if success { +// +// print("Navigation successful") +// +// } else { +// +// print("Navigation failed: \(errorMessage ?? "Unknown error")") +// +// } +// +// +// +// } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fe0b4d5..50be532 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -398,6 +398,22 @@ var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_Get var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord'; var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions'; +///Push Notifications +var GET_ALL_NOTIFICATIONS = 'Services/MobileNotifications.svc/REST/PushNotification_GetAllNotifications'; + +///My Trackers +var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; +var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; +var ADD_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; + +var GET_BLOOD_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; +var GET_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; +var ADD_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; + +var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; +var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; +var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; + var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult'; @@ -658,10 +674,12 @@ const FAMILY_FILES = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStat var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatbot_SendMedicationInstructionByWhatsApp'; +const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; + class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index d378c78..26da9fa 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -225,6 +225,9 @@ class AppAssets { static const String trade_down_red = '$svgBasePath/trade_down_red.svg'; static const String pharmacy_icon = '$svgBasePath/phramacy_icon.svg'; + static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg'; + static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 5f2444f..0763e8e 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -35,12 +35,15 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart'; +import 'package:hmg_patient_app_new/features/monthly_reports/monthly_reports_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; @@ -170,6 +173,7 @@ class AppDependencies { cacheService: getIt(), ), ); + getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -289,14 +293,12 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: 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())); + getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt())); - getIt.registerLazySingleton(() => MyInvoicesViewModel( - myInvoicesRepo: getIt(), - errorHandlerService: getIt(), - navServices: getIt(), - )); + + getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton( () => ActivePrescriptionsViewModel( @@ -304,13 +306,13 @@ class AppDependencies { activePrescriptionsRepo: getIt() ), ); - // getIt.registerFactory( - // () => QrParkingViewModel( - // qrParkingRepo: getIt(), - // errorHandlerService: getIt(), - // cacheService: getIt(), - // ), - // ); + getIt.registerFactory( + () => QrParkingViewModel( + qrParkingRepo: getIt(), + errorHandlerService: getIt(), + cacheService: getIt(), + ), + ); } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index f5ffd36..8978fcd 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -43,7 +43,7 @@ class Utils { { "Desciption": "Sahafa Hospital", "DesciptionN": "مستشفى الصحافة", - "ID": 1, + "ID": 1, // Campus ID "LegalName": "Sahafa Hospital", "LegalNameN": "مستشفى الصحافة", "Name": "Sahafa Hospital", @@ -55,19 +55,20 @@ class Utils { "IsActive": true, "IsHmg": true, "IsVidaPlus": false, - "Latitude": "24.8113774", - "Longitude": "46.6239813", + "Latitude": "24.8111548", + "Longitude": "46.6217912", "MainProjectID": 130, "ProjectOutSA": false, - "UsingInDoctorApp": false + "UsingInDoctorApp": false, + "IsHMC": false },{ - "Desciption": "Jeddah Hospital", - "DesciptionN": "مستشفى الصحافة", - "ID": 3, - "LegalName": "Jeddah Hospital", - "LegalNameN": "مستشفى الصحافة", - "Name": "Jeddah Hospital", - "NameN": "مستشفى الصحافة", + "Desciption": "Jeddah Fayhaa Hospital", + "DesciptionN": "مستشفى جدة الفيحاء", + "ID": 3, // Campus ID + "LegalName": "Jeddah Fayhaa Hospital", + "LegalNameN": "مستشفى جدة الفيحاء", + "Name": "Jeddah Fayhaa Hospital", + "NameN": "مستشفى جدة الفيحاء", "PhoneNumber": "+966115222222", "SetupID": "013311", "DistanceInKilometers": 0, @@ -75,11 +76,12 @@ class Utils { "IsActive": true, "IsHmg": true, "IsVidaPlus": false, - "Latitude": "24.8113774", - "Longitude": "46.6239813", + "Latitude": "21.5086703", + "Longitude": "39.2121855", "MainProjectID": 130, "ProjectOutSA": false, - "UsingInDoctorApp": false + "UsingInDoctorApp": false, + "IsHMC": false } ]; diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index a225c1d..3f5517a 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -289,9 +289,24 @@ class BookAppointmentsViewModel extends ChangeNotifier { this.isNearestAppointmentSelected = isNearestAppointmentSelected; if (isNearestAppointmentSelected) { - doctorsList.sort((a, b) => DateUtil.convertStringToDate(a.nearestFreeSlot!).compareTo(DateUtil.convertStringToDate(b.nearestFreeSlot!))); + for (var group in doctorsListGrouped) { + group.sort((a, b) { + var aSlot = a.nearestFreeSlot; + var bSlot = b.nearestFreeSlot; + if (aSlot == null || bSlot == null) return 0; + return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot)); + }); + } } else { - doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); + for (var group in doctorsListGrouped) { + group.sort((a, b) { + var aSlot = a.decimalDoctorRate; + var bSlot = b.decimalDoctorRate; + if (aSlot == null || bSlot == null) return 0; + return bSlot.compareTo(aSlot); + }); + } + // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); } notifyListeners(); diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index c55a11c..daddbff 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -40,6 +40,8 @@ class HmgServicesViewModel extends ChangeNotifier { bool isHospitalListLoading = false; bool isVitalSignLoading = false; + bool hasVitalSignDataLoaded = false; + // HHC specific loading states bool isHhcOrdersLoading = false; bool isHhcServicesLoading = false; @@ -878,6 +880,7 @@ class HmgServicesViewModel extends ChangeNotifier { Function(dynamic)? onSuccess, Function(String)? onError, }) async { + if (hasVitalSignDataLoaded) return; isVitalSignLoading = true; notifyListeners(); @@ -896,6 +899,7 @@ class HmgServicesViewModel extends ChangeNotifier { isVitalSignLoading = false; if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; + hasVitalSignDataLoaded = true; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index dd9531f..c0cab2a 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.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/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; @@ -17,20 +19,32 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { int hmgCount = 0; TextEditingController searchController = TextEditingController(); final AppState appState; + LocationUtils locationUtils = getIt.get(); HospitalSelectionBottomSheetViewModel(this.appState) { - Utils.navigationProjectsList.forEach((element) { - HospitalsModel model = HospitalsModel.fromJson(element); - if (model.isHMC == true) { - hmcHospitalList.add(model); - } else { - hmgHospitalList.add(model); - } - listOfData.add(model); + locationUtils.getCurrentLocation(onSuccess: (value) { + Utils.navigationProjectsList.forEach((element) { + HospitalsModel model = HospitalsModel.fromJson(element); + + double dist = Utils.distance(value.latitude, value.longitude, double.parse(model.latitude!), double.parse(model.longitude!)).ceilToDouble(); + print(dist); + model.distanceInKilometers = dist; + + if (model.isHMC == true) { + hmcHospitalList.add(model); + } else { + hmgHospitalList.add(model); + } + listOfData.add(model); + }); + hmgCount = hmgHospitalList.length; + hmcCount = hmcHospitalList.length; + + listOfData.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + hmgHospitalList.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + + getDisplayList(); }); - hmgCount = hmgHospitalList.length; - hmcCount = hmcHospitalList.length; - getDisplayList(); } getDisplayList() { @@ -79,13 +93,14 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { } - void openPenguin(HospitalsModel hospital) { - initPenguinSDK(hospital.iD); + void openPenguin(HospitalsModel hospital, {String clinicID = ""}) { + initPenguinSDK(hospital.iD, clinicID: clinicID); } - initPenguinSDK(int projectID) async { + initPenguinSDK(int projectID, {String clinicID = ""}) async { NavigationClinicDetails data = NavigationClinicDetails(); data.projectId = projectID.toString(); + data.clinicId = clinicID; final bool permited = await AppPermission.askPenguinPermissions(); if (!permited) { Map statuses = await [ diff --git a/lib/features/notifications/models/resp_models/notification_response_model.dart b/lib/features/notifications/models/resp_models/notification_response_model.dart new file mode 100644 index 0000000..034e19c --- /dev/null +++ b/lib/features/notifications/models/resp_models/notification_response_model.dart @@ -0,0 +1,96 @@ +class NotificationResponseModel { + int? id; + int? recordId; + int? patientID; + bool? projectOutSA; + String? deviceType; + String? deviceToken; + String? message; + String? messageType; + String? messageTypeData; + String? videoURL; + bool? isQueue; + String? isQueueOn; + String? createdOn; + dynamic createdBy; + String? notificationType; + bool? isSent; + String? isSentOn; + bool? isRead; + dynamic isReadOn; + int? channelID; + int? projectID; + + NotificationResponseModel( + {this.id, + this.recordId, + this.patientID, + this.projectOutSA, + this.deviceType, + this.deviceToken, + this.message, + this.messageType, + this.messageTypeData, + this.videoURL, + this.isQueue, + this.isQueueOn, + this.createdOn, + this.createdBy, + this.notificationType, + this.isSent, + this.isSentOn, + this.isRead, + this.isReadOn, + this.channelID, + this.projectID}); + + NotificationResponseModel.fromJson(Map json) { + id = json['Id']; + recordId = json['RecordId']; + patientID = json['PatientID']; + projectOutSA = json['ProjectOutSA']; + deviceType = json['DeviceType']; + deviceToken = json['DeviceToken']; + message = json['Message']; + messageType = json['MessageType']; + messageTypeData = json['MessageTypeData']; + videoURL = json['VideoURL']; + isQueue = json['IsQueue']; + isQueueOn = json['IsQueueOn']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + notificationType = json['NotificationType']; + isSent = json['IsSent']; + isSentOn = json['IsSentOn']; + isRead = json['IsRead']; + isReadOn = json['IsReadOn']; + channelID = json['ChannelID']; + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['Id'] = this.id; + data['RecordId'] = this.recordId; + data['PatientID'] = this.patientID; + data['ProjectOutSA'] = this.projectOutSA; + data['DeviceType'] = this.deviceType; + data['DeviceToken'] = this.deviceToken; + data['Message'] = this.message; + data['MessageType'] = this.messageType; + data['MessageTypeData'] = this.messageTypeData; + data['VideoURL'] = this.videoURL; + data['IsQueue'] = this.isQueue; + data['IsQueueOn'] = this.isQueueOn; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['NotificationType'] = this.notificationType; + data['IsSent'] = this.isSent; + data['IsSentOn'] = this.isSentOn; + data['IsRead'] = this.isRead; + data['IsReadOn'] = this.isReadOn; + data['ChannelID'] = this.channelID; + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/features/notifications/notifications_repo.dart b/lib/features/notifications/notifications_repo.dart new file mode 100644 index 0000000..853a979 --- /dev/null +++ b/lib/features/notifications/notifications_repo.dart @@ -0,0 +1,78 @@ +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/features/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class NotificationsRepo { + Future>>> getAllNotifications({ + required int notificationStatusID, + required int pagingSize, + required int currentPage, + }); +} + +class NotificationsRepoImp implements NotificationsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + NotificationsRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getAllNotifications({ + required int notificationStatusID, + required int pagingSize, + required int currentPage, + }) async { + Map mapDevice = { + "NotificationStatusID": notificationStatusID, + "pagingSize": pagingSize, + "currentPage": currentPage, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_ALL_NOTIFICATIONS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_GetAllNotificationsFromPool']; + if (list == null || list.isEmpty) { + // Return empty list if no notifications + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: [], + ); + return; + } + + final notifications = list.map((item) => NotificationResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: notifications, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/notifications/notifications_view_model.dart b/lib/features/notifications/notifications_view_model.dart new file mode 100644 index 0000000..e15e267 --- /dev/null +++ b/lib/features/notifications/notifications_view_model.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class NotificationsViewModel extends ChangeNotifier { + bool isNotificationsLoading = false; + bool hasMoreNotifications = true; + + NotificationsRepo notificationsRepo; + ErrorHandlerService errorHandlerService; + + List notificationsList = []; + + int currentPage = 0; + int pagingSize = 14; + int notificationStatusID = 2; // Default to status 2 (e.g., unread) + + NotificationsViewModel({ + required this.notificationsRepo, + required this.errorHandlerService, + }); + + initNotificationsViewModel({int? statusID}) { + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + isNotificationsLoading = true; + // if (statusID != null) { + // notificationStatusID = statusID; + // } + // getAllNotifications(); + notifyListeners(); + } + + setNotificationStatusID(int statusID) { + notificationStatusID = statusID; + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + notifyListeners(); + } + + Future getAllNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isNotificationsLoading = true; + notifyListeners(); + + final result = await notificationsRepo.getAllNotifications( + notificationStatusID: notificationStatusID, + pagingSize: pagingSize, + currentPage: currentPage, + ); + + result.fold( + (failure) async { + isNotificationsLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isNotificationsLoading = false; + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } + } else if (apiResponse.messageStatus == 1) { + final newNotifications = apiResponse.data ?? []; + + if (newNotifications.isEmpty || newNotifications.length < pagingSize) { + hasMoreNotifications = false; + } + + notificationsList.addAll(newNotifications); + currentPage++; + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future refreshNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notificationsList.clear(); + currentPage = 0; + hasMoreNotifications = true; + await getAllNotifications(onSuccess: onSuccess, onError: onError); + } + + Future loadMoreNotifications({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + await getAllNotifications(onSuccess: onSuccess, onError: onError); + } + + @override + void dispose() { + super.dispose(); + } +} diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart index 008b22c..76a4438 100644 --- a/lib/features/todo_section/todo_section_repo.dart +++ b/lib/features/todo_section/todo_section_repo.dart @@ -48,6 +48,8 @@ abstract class TodoSectionRepo { }); Future> applePayInsertRequest({required dynamic applePayInsertRequest}); + + Future> getPatientDashboard(); } class TodoSectionRepoImp implements TodoSectionRepo { @@ -376,4 +378,29 @@ class TodoSectionRepoImp implements TodoSectionRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getPatientDashboard() async { + Map mapDevice = {}; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + DASHBOARD, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in applePayInsertRequest: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index c0fb96b..92d893a 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -10,11 +10,15 @@ class TodoSectionViewModel extends ChangeNotifier { TodoSectionViewModel({required this.todoSectionRepo, required this.errorHandlerService}); + String? notificationsCount = "0"; + initializeTodoSectionViewModel() async { patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; isAncillaryDetailsProceduresLoading = true; - await getPatientOnlineAncillaryOrderList(); + notificationsCount = "0"; + getPatientOnlineAncillaryOrderList(); + getPatientDashboard(); } bool isAncillaryOrdersLoading = false; @@ -28,6 +32,28 @@ class TodoSectionViewModel extends ChangeNotifier { notifyListeners(); } + Future getPatientDashboard({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await todoSectionRepo.getPatientDashboard(); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + // if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + // } else if (apiResponse.messageStatus == 1) { + notificationsCount = + apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + // } + }, + ); + } + Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; diff --git a/lib/main.dart b/lib/main.dart index e730e07..5158706 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; @@ -184,6 +185,9 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), ), diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index eb6d06f..9f2da16 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -262,7 +262,6 @@ class _SearchDoctorByNameState extends State { final doctorIndex = entry.key; final doctor = entry.value; final isLastDoctor = doctorIndex == groupedDoctors[index].length - 1; - return Column( children: [ DoctorCard( diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 28b0f61..2f8747d 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -198,7 +198,8 @@ class _SelectDoctorPageState extends State { physics: NeverScrollableScrollPhysics(), itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.doctorsListGrouped.isNotEmpty ? bookAppointmentsVM.doctorsListGrouped.length : 1), itemBuilder: (context, index) { - final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + // final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + final isExpanded = true; return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( doctorsListResponseModel: DoctorsListResponseModel(), diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 0f30c5f..d2e1c0b 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -43,15 +43,44 @@ class DoctorCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - isLoading - ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" - : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", - width: 63.h, - height: 63.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: isLoading), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + isLoading + ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" + : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: isLoading), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, // Makes the container circular + border: Border.all( + color: AppColors.scaffoldBgColor, // Color of the border + width: 1.5.w, // Width of the border + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), + SizedBox(height: 2.h), + "${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100).toShimmer2(isShow: isLoading), + ), + ], + ), SizedBox(width: 8.h), Expanded( flex: 9, @@ -70,15 +99,19 @@ class DoctorCard extends StatelessWidget { ), SizedBox(height: 2.h), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (isLoading - ? "Consultant Cardiologist" - : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first - : "") - .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) - .toShimmer2(isShow: isLoading), + SizedBox( + width: MediaQuery.of(context).size.width * 0.45, + child: (isLoading + ? "Consultant Cardiologist" + : doctorsListResponseModel.speciality!.isNotEmpty + ? doctorsListResponseModel.speciality!.first + : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 2) + .toShimmer2(isShow: isLoading), + ), SizedBox(width: 6.w), Image.network( isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", @@ -98,23 +131,17 @@ class DoctorCard extends StatelessWidget { ), ], ), - SizedBox(height: 12.h), Wrap( direction: Axis.horizontal, spacing: 3.h, runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: "Clinic: ${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, + labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: "Branch: ${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, + labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, ).toShimmer2(isShow: isLoading), - doctorsListResponseModel.decimalDoctorRate != null ? AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".needTranslation, - ).toShimmer2(isShow: isLoading) : SizedBox(), bookAppointmentsViewModel.isNearestAppointmentSelected ? doctorsListResponseModel.nearestFreeSlot != null ? AppCustomChipWidget( diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index e49a621..5c028db 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -4,15 +4,18 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.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/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/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +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/generated/locale_keys.g.dart'; @@ -38,6 +41,7 @@ import 'package:provider/provider.dart'; 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 { ServicesPage({super.key}); @@ -46,27 +50,24 @@ class ServicesPage extends StatelessWidget { late MedicalFileViewModel medicalFileViewModel; late final List hmgServices = [ - HmgServicesComponentModel( - 11, - "Emergency Services".needTranslation, - "".needTranslation, - AppAssets.emergency_services_icon, - bgColor: AppColors.primaryRedColor, - true, - route: null, onTap: () { - getIt.get().flushData(); - getIt.get().getTransportationOrders( - showLoader: false, - ); - getIt.get().getRRTOrders( - showLoader: false, - ); - Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( - CustomPageRoute( - page: EmergencyServicesPage(), - settings: const RouteSettings(name: '/EmergencyServicesPage'), - ), - ); + HmgServicesComponentModel(11, "Emergency Services".needTranslation, "".needTranslation, 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.instance().navigatorKey.currentContext!).push( + CustomPageRoute( + page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } }), HmgServicesComponentModel( 11, @@ -77,15 +78,13 @@ class ServicesPage extends StatelessWidget { true, route: AppRoutes.bookAppointmentPage, ), - HmgServicesComponentModel( - 5, - "Complete Checkup".needTranslation, - "".needTranslation, - AppAssets.comprehensiveCheckup, - bgColor: AppColors.bgGreenColor, - true, - route: AppRoutes.comprehensiveCheckupPage, - ), + HmgServicesComponentModel(5, "Complete Checkup".needTranslation, "".needTranslation, AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); + } else { + await getIt.get().onLoginPressed(); + } + }), HmgServicesComponentModel( 11, "Indoor Navigation".needTranslation, @@ -94,17 +93,51 @@ class ServicesPage extends StatelessWidget { bgColor: Color(0xff45A2F8), true, route: null, - onTap: () {}, + onTap: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + GetIt.instance().navigatorKey.currentContext!, + child: ChangeNotifierProvider( + create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + vm.openPenguin(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + GetIt.instance().navigatorKey.currentContext!.read().clearSearchText(); + }, + ); + }, ), HmgServicesComponentModel( - 11, - "E-Referral Services".needTranslation, - "".needTranslation, - AppAssets.eReferral, - bgColor: AppColors.eReferralCardColor, - true, - route: AppRoutes.eReferralPage, - ), + 11, "E-Referral Services".needTranslation, "".needTranslation, AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.eReferralPage); + } else { + await getIt.get().onLoginPressed(); + } + }), HmgServicesComponentModel( 3, "Blood Donation".needTranslation, @@ -182,7 +215,14 @@ class ServicesPage extends StatelessWidget { AppAssets.general_health, bgColor: AppColors.whiteColor, true, - route: AppRoutes.healthTrackersPage, + route: null, + onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.healthTrackersPage); + } else { + await getIt.get().onLoginPressed(); + } + }, ), HmgServicesComponentModel( 11, @@ -193,24 +233,28 @@ class ServicesPage extends StatelessWidget { true, route: null, // Set to null since we handle navigation in onTap onTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); - final waterMonitorVM = getIt.get(); - final context = getIt.get().navigatorKey.currentContext!; - await waterMonitorVM.fetchUserDetailsForMonitoring( - onSuccess: (userDetail) { - LoaderBottomSheet.hideLoader(); - if (userDetail == null) { - waterMonitorVM.populateFromAuthenticatedUser(); - context.navigateWithName(AppRoutes.waterMonitorSettingsPage); - } else { + if (getIt.get().isAuthenticated) { + LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); + final waterMonitorVM = getIt.get(); + final context = getIt.get().navigatorKey.currentContext!; + await waterMonitorVM.fetchUserDetailsForMonitoring( + onSuccess: (userDetail) { + LoaderBottomSheet.hideLoader(); + if (userDetail == null) { + waterMonitorVM.populateFromAuthenticatedUser(); + context.navigateWithName(AppRoutes.waterMonitorSettingsPage); + } else { + context.navigateWithName(AppRoutes.waterConsumptionPage); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); context.navigateWithName(AppRoutes.waterConsumptionPage); - } - }, - onError: (error) { - LoaderBottomSheet.hideLoader(); - context.navigateWithName(AppRoutes.waterConsumptionPage); - }, - ); + }, + ); + } else { + await getIt.get().onLoginPressed(); + } }, ), HmgServicesComponentModel( @@ -238,7 +282,14 @@ class ServicesPage extends StatelessWidget { AppAssets.smartwatch_icon, bgColor: AppColors.whiteColor, true, - route: AppRoutes.smartWatches, + route: null, + onTap: () async { + if (getIt.get().isAuthenticated) { + getIt.get().pushPageRoute(AppRoutes.smartWatches); + } else { + await getIt.get().onLoginPressed(); + } + }, // route: AppRoutes.huaweiHealthExample, ), ]; @@ -331,14 +382,17 @@ class ServicesPage extends StatelessWidget { ], ), Spacer(), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }), + getIt.get().isAuthenticated + ? Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) + : "Login to view your wallet balance".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), - CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, iconSize: 16.w, iconColor: AppColors.infoColor, textColor: AppColors.infoColor, @@ -352,10 +406,15 @@ class ServicesPage extends StatelessWidget { onPressed: () { Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); }, - ), + ) + : SizedBox.shrink(), ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + } else { + await getIt.get().onLoginPressed(); + } }), ), ), @@ -377,13 +436,14 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), - "Medical Files".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.medicalFile.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), ], ), Spacer(), - Wrap( - spacing: -8.h, + getIt.get().isAuthenticated + ? Wrap( + spacing: -8.h, // runSpacing: 0.h, children: [ Utils.buildImgWithAssets( @@ -411,10 +471,12 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), ], - ), + ) + : "Login to view your medical file".needTranslation.toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), - CustomButton( - height: 40.h, + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, icon: AppAssets.add_icon, iconSize: 16.w, iconColor: AppColors.primaryRedColor, @@ -436,14 +498,19 @@ class ServicesPage extends StatelessWidget { medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); }, - ), + ) + : SizedBox.shrink(), ], - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - ), - ); + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } }), ), ), @@ -539,7 +606,6 @@ class ServicesPage extends StatelessWidget { ), ), ); - }), ), ), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index c693479..ef2832b 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -46,7 +46,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "health_converters", - icon: AppAssets.health_calculators_icon, + icon: AppAssets.health_converters_icon, title: "Health", subtitle: "Converters", backgroundColor: AppColors.whiteColor, @@ -56,7 +56,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "parking_guide", - icon: AppAssets.health_calculators_icon, + icon: AppAssets.car_parking_icon, title: "Parking", subtitle: "Guide", backgroundColor: AppColors.whiteColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 6d7a91c..789bc4d 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -21,11 +21,13 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.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/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -44,6 +46,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/notifications/notifications_list_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; @@ -61,6 +64,8 @@ import 'package:provider/provider.dart'; import '../active_medication/active_medication_page.dart'; +import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; + class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -82,6 +87,7 @@ class _LandingPageState extends State { late BookAppointmentsViewModel bookAppointmentsViewModel; late EmergencyServicesViewModel emergencyServicesViewModel; late TodoSectionViewModel todoSectionViewModel; + late NotificationsViewModel notificationsViewModel; final SwiperController _controller = SwiperController(); @@ -108,22 +114,26 @@ class _LandingPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); emergencyServicesViewModel.checkPatientERAdvanceBalance(); myAppointmentsViewModel.getPatientAppointmentQueueDetails(); - if(!appState.isRatedVisible) { - appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { - if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, - onSuccess: ((response) { - appointmentRatingViewModel.setClinicOrDoctor(false); - appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation); - appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation); - openLastRating(); - appState.setRatedVisible(true); - }), - ); - } - }, - ); - } + notificationsViewModel.initNotificationsViewModel(); + + // Commented as per new requirement to remove rating popup from the app + + // if(!appState.isRatedVisible) { + // appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { + // if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + // appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, + // onSuccess: ((response) { + // appointmentRatingViewModel.setClinicOrDoctor(false); + // appointmentRatingViewModel.setTitle("Rate Doctor".needTranslation); + // appointmentRatingViewModel.setSubTitle("How was your last visit with doctor?".needTranslation); + // openLastRating(); + // appState.setRatedVisible(true); + // }), + // ); + // } + // }, + // ); + // } } }); super.initState(); @@ -137,6 +147,7 @@ class _LandingPageState extends State { immediateLiveCareViewModel = Provider.of(context, listen: false); emergencyServicesViewModel = Provider.of(context, listen: false); todoSectionViewModel = Provider.of(context, listen: false); + notificationsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return PopScope( canPop: false, @@ -182,37 +193,65 @@ class _LandingPageState extends State { padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, ), - Row( - mainAxisSize: MainAxisSize.min, - spacing: 12.h, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }), - Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: MedicalFilePage(), - // // page: LoginScreen(), - // ), - // ); - }), - Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, - ); - }), - ], - ), + Consumer(builder: (context, todoSectionVM, child) { + return Row( + mainAxisSize: MainAxisSize.min, + spacing: 12.h, + children: [ + Stack(children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() async { + if (appState.isAuthenticated) { + notificationsViewModel.setNotificationStatusID(2); + notificationsViewModel.getAllNotifications(); + Navigator.of(context).push( + CustomPageRoute( + page: NotificationsListPage(), + // page: LoginScreen(), + ), + ); + } else { + await authVM.onLoginPressed(); + } + }), + (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) + ? Positioned( + right: 0, + top: 0, + child: Container( + width: 8.w, + height: 8.h, + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + "", + style: TextStyle( + color: Colors.white, + fontSize: 8.f, + ), + textAlign: TextAlign.center, + ), + ), + ) + : SizedBox.shrink(), + ]), + Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() { + openIndoorNavigationBottomSheet(context); + }), + Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + ], + ); + }), ], ).paddingSymmetrical(24.h, 0.h), !appState.isAuthenticated @@ -312,15 +351,49 @@ class _LandingPageState extends State { isFromHomePage: true, ), ).paddingSymmetrical(24.h, 0.h) - : Swiper( - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, - layout: SwiperLayout.STACK, - loop: true, - itemWidth: MediaQuery.of(context).size.width - 48.h, + : isTablet + ? SizedBox( + height: isFoldable ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 3, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox( + width: 10.w, + ), + ), + ) + : Swiper( + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 3, + layout: SwiperLayout.STACK, + loop: true, + itemWidth: MediaQuery.of(context).size.width - 48.h, indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, @@ -331,208 +404,7 @@ class _LandingPageState extends State { builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return (myAppointmentsVM.isPatientHasQueueAppointment && index == 0) - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: myAppointmentsVM.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, - backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), - textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - ], - ), - SizedBox(height: 8.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 2.h), - "Thank you for your patience, here is your queue number." - .needTranslation - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 8.h), - myAppointmentsVM.currentPatientQueueDetails.queueNo!.toText28(isBold: true), - SizedBox(height: 6.h), - myAppointmentsVM.patientQueueDetailsList.isNotEmpty ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - "Serving Now: ".needTranslation.toText14(isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - myAppointmentsVM.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), - SizedBox(width: 8.w), - AppCustomChipWidget( - deleteIcon: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, - labelText: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, - iconColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - textColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, - iconSize: 14.w, - backgroundColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor.withValues(alpha: 0.1) : AppColors.successColor.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), - ), - ], - ), - ], - ) : SizedBox(height: 12.h), - SizedBox(height: 5.h), - CustomButton( - text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), - onPressed: () {}, - backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), - borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), - textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus), - fontSize: 12.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ), - ], - ), - ), - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: AppointmentQueuePage(), - ), - ); - }) - : (immediateLiveCareVM.patientHasPendingLiveCareRequest && index == 0) - ? Column( - children: [ - SizedBox(height: 12.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Immediate LiveCare Request".needTranslation.toText16(isBold: true), - SizedBox(height: 10.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - AppCustomChipWidget( - labelText: immediateLiveCareVM.patientLiveCareHistoryList[0].stringCallStatus, - backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), - textColor: AppColors.alertColor, - ), - SizedBox(width: 8.w), - AppCustomChipWidget( - icon: AppAssets.appointment_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(immediateLiveCareVM.patientLiveCareHistoryList[0].arrivalTime), false)), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), - ], - ), - SizedBox(height: 10.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), - SizedBox(height: 8.h), - "Your turn is after ${immediateLiveCareVM.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), - SizedBox(height: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Expected waiting time: ".needTranslation.toText12(isBold: true), - SizedBox(height: 7.h), - ValueListenableBuilder( - valueListenable: immediateLiveCareVM.durationNotifier, - builder: (context, duration, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTime(duration), - ], - ); - }, - ), - ], - ), - // CustomButton( - // text: "View Details".needTranslation, - // onPressed: () async { - // Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - // }, - // backgroundColor: Color(0xffFEE9EA), - // borderColor: Color(0xffFEE9EA), - // textColor: Color(0xffED1C2B), - // fontSize: 14.f, - // fontWeight: FontWeight.w500, - // borderRadius: 12.r, - // padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - // height: 40.h, - // ), - ], - ), - ), - ).paddingSymmetrical(0.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - }), - SizedBox(height: 12.h), - ], - ) - : (todoSectionVM.patientAncillaryOrdersList.isNotEmpty && index == 1) - ? AncillaryOrderCard( - order: todoSectionVM.patientAncillaryOrdersList.first, - isLoading: false, - isOrdersList: false, - onCheckIn: () { - log("Check-in for order: ${todoSectionVM.patientAncillaryOrdersList.first.orderNo}"); - }, - onViewDetails: () { - Navigator.of(context).push( - CustomPageRoute( - page: AncillaryOrderDetailsList( - appointmentNoVida: todoSectionVM.patientAncillaryOrdersList.first.appointmentNo ?? 0, - orderNo: todoSectionVM.patientAncillaryOrdersList.first.orderNo ?? 0, - projectID: todoSectionVM.patientAncillaryOrdersList.first.projectID ?? 0, - projectName: todoSectionVM.patientAncillaryOrdersList.first.projectName ?? "", - ), - ), - ); - }, - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: - myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); + return getIndexSwiperCard(index); }, ) : Container( @@ -773,6 +645,288 @@ class _LandingPageState extends State { ); } + Widget getIndexSwiperCard(int index) { + if (index == 0) { + if (myAppointmentsViewModel.isPatientHasQueueAppointment) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), width: 2.w), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: myAppointmentsViewModel.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, + backgroundColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.20), + textColor: Utils.getCardBorderColor(myAppointmentsViewModel.currentQueueStatus), + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + ], + ), + SizedBox(height: 8.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 2.h), + "Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + SizedBox(height: 8.h), + myAppointmentsViewModel.currentPatientQueueDetails.queueNo!.toText28(isBold: true), + SizedBox(height: 6.h), + myAppointmentsViewModel.patientQueueDetailsList.isNotEmpty + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + "Serving Now: ".needTranslation.toText14(isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + myAppointmentsViewModel.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, + iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 + ? AppColors.primaryRedColor.withValues(alpha: 0.1) + : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + ), + ], + ), + ], + ) + : SizedBox(height: 12.h), + SizedBox(height: 5.h), + CustomButton( + text: Utils.getCardButtonText(myAppointmentsViewModel.currentQueueStatus, myAppointmentsViewModel.currentPatientQueueDetails.roomNo ?? ""), + onPressed: () {}, + backgroundColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus), + borderColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.01), + textColor: Utils.getCardButtonTextColor(myAppointmentsViewModel.currentQueueStatus), + fontSize: 12.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + ], + ), + ), + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: AppointmentQueuePage(), + ), + ); + }); + } else if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + return Column( + children: [ + SizedBox(height: 12.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate LiveCare Request".needTranslation.toText16(isBold: true), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppCustomChipWidget( + labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), + ], + ), + SizedBox(height: 10.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".needTranslation.toText14(isBold: true), + SizedBox(height: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Expected waiting time: ".needTranslation.toText12(isBold: true), + SizedBox(height: 7.h), + ValueListenableBuilder( + valueListenable: immediateLiveCareViewModel.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(0.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + }), + SizedBox(height: 12.h), + ], + ); + } else if (myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } else if (index == 1) { + if (myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } else if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { + return AncillaryOrderCard( + order: todoSectionViewModel.patientAncillaryOrdersList.first, + isLoading: false, + isOrdersList: false, + onCheckIn: () { + log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); + }, + onViewDetails: () { + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderDetailsList( + appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, + orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, + projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, + projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", + ), + ), + ); + }, + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } else if (index == 2) { + if ((myAppointmentsViewModel.isPatientHasQueueAppointment || immediateLiveCareViewModel.patientHasPendingLiveCareRequest) && + myAppointmentsViewModel.patientUpcomingAppointmentsHistoryList.isNotEmpty) { + return AncillaryOrderCard( + order: todoSectionViewModel.patientAncillaryOrdersList.first, + isLoading: false, + isOrdersList: false, + onCheckIn: () { + log("Check-in for order: ${todoSectionViewModel.patientAncillaryOrdersList.first.orderNo}"); + }, + onViewDetails: () { + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderDetailsList( + appointmentNoVida: todoSectionViewModel.patientAncillaryOrdersList.first.appointmentNo ?? 0, + orderNo: todoSectionViewModel.patientAncillaryOrdersList.first.orderNo ?? 0, + projectID: todoSectionViewModel.patientAncillaryOrdersList.first.projectID ?? 0, + projectName: todoSectionViewModel.patientAncillaryOrdersList.first.projectName ?? "", + ), + ), + ); + }, + ); + } else { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsViewModel.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + } + } + return Container(); + } + void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( context, @@ -831,4 +985,41 @@ class _LandingPageState extends State { isFullScreen: false, ); } + + void openIndoorNavigationBottomSheet(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + context, + child: ChangeNotifierProvider( + create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.openPenguin(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().clearSearchText(); + }, + ); + } } diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 6216880..49db8bf 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -1,17 +1,22 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; +import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart'; 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/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'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -126,6 +131,39 @@ class SmallServiceCard extends StatelessWidget { case "indoor_navigation": openIndoorNavigationBottomSheet(context); + + case "search_doctor": + Navigator.of(context).push( + CustomPageRoute( + page: SearchDoctorByName(), + ), + ); + break; + case "health_calculators_and_converts": + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorsPage(type: HealthCalConEnum.calculator), + ), + ); + break; + case "health_converters": + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorsPage(type: HealthCalConEnum.converter), + ), + ); + break; + case "parking_guide": + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ChangeNotifierProvider( + create: (_) => getIt(), + child: const ParkingPage(), + ), + ), + ); + break; default: // Handle unknown service break; diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart new file mode 100644 index 0000000..99d4270 --- /dev/null +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.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/extensions/int_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class NotificationsListPage extends StatelessWidget { + const NotificationsListPage({super.key}); + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Notifications".needTranslation, + child: SingleChildScrollView( + child: Consumer(builder: (context, notificationsVM, child) { + return Container( + margin: EdgeInsets.symmetric(vertical: 24.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: ListView.builder( + itemCount: notificationsVM.isNotificationsLoading ? 4 : notificationsVM.notificationsList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return notificationsVM.isNotificationsLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + // "Notification Title".toText14(), + // SizedBox(height: 8.h), + Row( + children: [ + Expanded(child: notificationsVM.notificationsList[index].message!.toText16(isBold: notificationsVM.notificationsList[index].isRead ?? false)), + ], + ), + SizedBox(height: 12.h), + DateUtil.formatDateToDate(DateUtil.convertStringToDate(notificationsVM.notificationsList[index].isSentOn!), false).toText14(weight: FontWeight.w500), + 1.divider, + ], + ), + ), + ), + ), + ); + }).paddingSymmetrical(16.w, 0.h), + ).paddingSymmetrical(24.w, 0.h); + }), + ), + ); + } +} diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index f112d51..ca72c6e 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -56,7 +56,7 @@ class _ParkingPageState extends State { children: [ Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.symmetric(horizontal: 24.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index e463ae7..62f090e 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -217,12 +217,12 @@ class ProfileSettingsState extends State { child: Column( children: [ actionItem(AppAssets.email_transparent, "Update Email Address".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), - 1.divider, - actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), + // 1.divider, + // actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), ], ), ), diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index fdfcc04..054108d 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -266,7 +266,11 @@ class _AncillaryOrderPaymentPageState extends State { fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { if (!todoSectionViewModel.isProcessingPayment) { - _startApplePay(); + if (Utils.havePrivilege(103)) { + _startApplePay(); + } else { + _openPaymentURL("ApplePay"); + } } }) : SizedBox(height: 12.h), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 632463f..778757d 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -138,6 +138,6 @@ class AppRoutes { qrParking: (context) => ChangeNotifierProvider( create: (_) => getIt(), child: const ParkingPage(), - ), + ),} }; } diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart index b3bc3eb..942a93f 100644 --- a/lib/widgets/appbar/app_bar_widget.dart +++ b/lib/widgets/appbar/app_bar_widget.dart @@ -42,15 +42,15 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { ? RotatedBox( quarterTurns: 90, child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32.w, - height: 32.h, + icon: AppAssets.forward_top_nav_icon, + width: 24.w, + height: 24.h, ), ) : Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32.w, - height: 32.h, + icon: AppAssets.forward_top_nav_icon, + width: 24.w, + height: 24.h, ), ), ), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 5409fcf..0580776 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -89,7 +89,7 @@ class CollapsingListView extends StatelessWidget { ? Transform.flip( flipX: appState.isArabic(), child: IconButton( - icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 24.h, height: 24.h), + icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.forward_top_nav_icon, width: 24.h, height: 24.h), padding: EdgeInsets.only(left: 12), onPressed: () { if (leadingCallback != null) {