diff --git a/assets/images/svg/location_red.svg b/assets/images/svg/location_red.svg
new file mode 100644
index 0000000..ba8c131
--- /dev/null
+++ b/assets/images/svg/location_red.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/location_unavailable.svg b/assets/images/svg/location_unavailable.svg
new file mode 100644
index 0000000..0ba34eb
--- /dev/null
+++ b/assets/images/svg/location_unavailable.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json
index fd34e55..7b7d83b 100644
--- a/assets/langs/ar-SA.json
+++ b/assets/langs/ar-SA.json
@@ -818,4 +818,11 @@
"ready": "جاهز",
"enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح",
"enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح"
+ "medicalCentersWithCount": "{count} مراكز طبية",
+ "medicalCenters": "مراكز طبية",
+ "hospitalsWithCount": "{count} مستشفيات",
+ "selectRegion": "اختر المنطقة",
+ "selectFacility": "اختر المرافق",
+ "selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد",
+ "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد"
}
\ No newline at end of file
diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json
index b4cd340..17b046e 100644
--- a/assets/langs/en-US.json
+++ b/assets/langs/en-US.json
@@ -813,5 +813,12 @@
"enterValidNationalId": "Please enter a valid national ID or file number",
"enterValidPhoneNumber": "Please enter a valid phone number",
"ready": "Ready",
- "news": "News"
+ "medicalCentersWithCount" : "{count} Medical Centers",
+ "medicalCenters" : " Medical Centers",
+ "hospitalsWithCount" : "{count} Hospitals",
+ "selectRegion": "Select Region",
+ "selectFacility": "Select Facilities",
+ "selectFacilitiesSubTitle": "Please select the facility for the appointment",
+ "selectHospitalSubTitle": "Please select the hospital for the appointment"
+
}
\ No newline at end of file
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index bad2e4c..bd2eb71 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -56,7 +56,7 @@ var RC_BASE_URL = 'https://rc.hmg.com/';
var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity';
-var GET_PROJECT = 'Services/Lists.svc/REST/GetProject';
+var GET_PROJECT_LIST = 'Services/Lists.svc/REST/GetProject';
///Geofencing
var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints';
@@ -217,9 +217,6 @@ var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDo
//URL to get active appointment list
var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber";
-//URL to get projects list
-var GET_PROJECTS_LIST = 'Services/Lists.svc/REST/GetProject';
-
//URL to get doctors list
var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime";
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 7fbd212..4a8a3da 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -108,6 +108,8 @@ class AppAssets {
static const String search_by_clinic_icon = '$svgBasePath/search_by_clinic_icon.svg';
static const String search_by_doctor_icon = '$svgBasePath/search_by_doctor_icon.svg';
static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg';
+ static const String location_red = '$svgBasePath/location_red.svg';
+ static const String location_unavailable = '$svgBasePath/location_unavailable.svg';
static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg';
static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg';
static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg';
diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart
index 52500bf..5b08406 100644
--- a/lib/core/app_state.dart
+++ b/lib/core/app_state.dart
@@ -132,4 +132,6 @@ class AppState {
set setUserRegistrationPayload(RegistrationDataModelPayload value) {
_userRegistrationPayload = value;
}
+
+
}
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 90e3702..f5b3d05 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -128,6 +128,7 @@ class AppDependencies {
() => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
+ appState: getIt()
),
);
diff --git a/lib/core/enums.dart b/lib/core/enums.dart
index 2f0b078..05a6f43 100644
--- a/lib/core/enums.dart
+++ b/lib/core/enums.dart
@@ -29,7 +29,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CalenderEnum { gregorian, hijri }
-enum SelectionTypeEnum { dropdown, calendar }
+enum SelectionTypeEnum { dropdown, calendar, search }
enum GenderTypeEnum { male, female }
diff --git a/lib/core/utils/doctor_response_mapper.dart b/lib/core/utils/doctor_response_mapper.dart
new file mode 100644
index 0000000..fd0cb79
--- /dev/null
+++ b/lib/core/utils/doctor_response_mapper.dart
@@ -0,0 +1,207 @@
+import 'dart:math';
+
+import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst;
+import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart' show RegionList, PatientDoctorAppointmentList, DoctorList, PatientDoctorAppointmentListByRegion;
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
+
+class DoctorMapper{
+ static Future getMappedDoctor(List doctorList,
+ {bool isArabic = false}) async {
+ RegionList regionList = RegionList();
+ final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
+ final long = await Utils.getNumFromPrefs(CacheConst.userLat);
+
+ for (var element in doctorList) {
+ String? region = element.getRegionName(isArabic);
+ if (region == null) continue;
+
+ var regionDoctorList = regionList.registeredDoctorMap?.putIfAbsent(region, () => PatientDoctorAppointmentListByRegion());
+
+ List? targetList = element.isHMC == true
+ ? regionDoctorList?.hmcDoctorList
+ : regionDoctorList?.hmgDoctorList;
+
+ var doctorByHospital = targetList
+ ?.where((clinic) =>
+ clinic.filterName ==
+ element.getProjectCompleteNameWithLocale(isArabic: isArabic))
+ .toList() ??
+ [];
+
+ if (doctorByHospital.isNotEmpty) {
+ doctorByHospital.first.patientDoctorAppointmentList?.add(element);
+ } else {
+ var newAppointment = PatientDoctorAppointmentList(
+ filterName:
+ element.getProjectCompleteNameWithLocale(isArabic: isArabic),
+ distanceInKMs: element.projectDistanceInKiloMeters.toString(),
+ projectTopName: element.projectTopName,
+ projectBottomName: element.projectBottomName,
+ patientDoctorAppointment: element,
+ isHMC: element.isHMC
+ );
+ if(element.projectDistanceInKiloMeters!= null ){
+ if(regionDoctorList!.distance>element.projectDistanceInKiloMeters){
+ regionDoctorList.distance = element.projectDistanceInKiloMeters;
+ }
+ if (element.isHMC == true &&
+ element.projectDistanceInKiloMeters <
+ regionDoctorList.hmcDistance) {
+ regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters;
+ } else if (element.projectDistanceInKiloMeters <
+ regionDoctorList.hmgDistance) {
+ regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters;
+ }
+ }else
+ if (lat != 0&&
+ long != 0 && element.latitude != null && element.longitude != null) {
+
+
+ double distance = calculateDistance(lat.toDouble(), long.toDouble(), double.parse(element.latitude!), double.parse(element.longitude!));
+ if(distance<0){
+ distance *= -1;
+ }
+ if(regionDoctorList!.distance>distance){
+ regionDoctorList.distance = distance;
+ }
+ if (element.isHMC == true &&
+ element.projectDistanceInKiloMeters <
+ regionDoctorList.hmcDistance) {
+ regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters;
+ } else if (element.projectDistanceInKiloMeters <
+ regionDoctorList.hmgDistance) {
+ regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters;
+ }
+ }
+ targetList?.add(newAppointment);
+ }
+
+
+
+
+ regionDoctorList?.hmcSize = regionDoctorList.hmcDoctorList?.length ?? 0;
+ regionDoctorList?.hmgSize = regionDoctorList.hmgDoctorList?.length ?? 0;
+
+ regionList.registeredDoctorMap?[region] = regionDoctorList;
+ }
+
+ return regionList;
+ }
+ static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+ var pi = 3.142;
+ const double R = 6371;
+ double dLat = (lat2 - lat1) * pi / 180;
+ double dLon = (lon2 - lon1) * pi / 180;
+ double a = sin(dLat / 2) * sin(dLat / 2) +
+ cos(lat1 * pi / 180) * cos(lat2 * pi / 180) * sin(dLon / 2) * sin(dLon / 2);
+ double c = 2 * atan2(sqrt(a), sqrt(1 - a));
+ return R * c;
+ }
+
+ static Future sortList(bool isGPSEnabled, RegionList unsorted, ) async {
+ if(isGPSEnabled){
+ if(unsorted.registeredDoctorMap == null) return unsorted;
+ var sortedMap = Map.fromEntries(
+ unsorted.registeredDoctorMap!.entries.toList()
+ ..sort((a, b) => a.value!.distance.compareTo(b.value!.distance)),
+ );
+
+ unsorted.registeredDoctorMap = sortedMap;
+ return unsorted;
+
+ }
+
+ List? keys = unsorted.registeredDoctorMap?.keys.toList();
+ keys?.sort();
+
+ if (keys == null) return unsorted;
+ Map sortedMap = {};
+ for (var key in keys) {
+ sortedMap[key] = unsorted.registeredDoctorMap![key]!;
+ }
+ unsorted.registeredDoctorMap = sortedMap;
+ return unsorted;
+ }
+
+ static Future getMappedHospitals(
+ List hospitalList, {
+ bool isArabic = false,
+ }) async {
+ final regionList = RegionList();
+ final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
+ final long = await Utils.getNumFromPrefs(CacheConst.userLat);
+ for (final hospital in hospitalList) {
+ final region = hospital.getRegionName(isArabic);
+ if (region == null) continue;
+
+ final regionData = regionList.registeredDoctorMap?.putIfAbsent(
+ region,
+ () => PatientDoctorAppointmentListByRegion(),
+ );
+
+ List? targetList = hospital.isHMC == true
+ ? regionData?.hmcDoctorList
+ : regionData?.hmgDoctorList;
+
+ List existingEntry = targetList
+ ?.where(
+ (entry) => entry.filterName == hospital.getName(isArabic),
+ )
+ .toList() ??
+ [];
+
+ if (existingEntry.isNotEmpty) {
+ existingEntry.first.hospitalList.add(hospital);
+ } else {
+ final newEntry = PatientDoctorAppointmentList(
+ filterName: hospital.name,
+ distanceInKMs: hospital.distanceInKilometers?.toString(),
+ projectTopName: hospital.name,
+ projectBottomName: hospital.name,
+ model: hospital,
+ isHMC: hospital.isHMC);
+
+ final distance = hospital.distanceInKilometers;
+
+ if (distance != null) {
+ if (regionData!.distance > distance) {
+ regionData.distance = distance;
+ }
+ if (hospital.isHMC == true && distance < regionData.hmcDistance) {
+ regionData.hmcDistance = distance;
+ } else if (distance < regionData.hmgDistance) {
+ regionData.hmgDistance = distance;
+ }
+ } else if ( lat != 0&&
+ long != 0 &&
+ hospital.latitude != null &&
+ hospital.longitude != null) {
+ double calculatedDistance = calculateDistance(
+ lat.toDouble(),
+ long.toDouble(),
+ double.parse(hospital.latitude!),
+ double.parse(hospital.longitude!),
+ ).abs();
+
+ if (regionData!.distance > calculatedDistance) {
+ regionData.distance = calculatedDistance;
+ }
+ if (hospital.isHMC == true &&
+ calculatedDistance < regionData.hmcDistance) {
+ regionData.hmcDistance = calculatedDistance;
+ } else if (calculatedDistance < regionData.hmgDistance) {
+ regionData.hmgDistance = calculatedDistance;
+ }
+ }
+ targetList?.add(newEntry);
+ }
+
+ regionData?.hmcSize = regionData.hmcDoctorList?.length ?? 0;
+ regionData?.hmgSize = regionData.hmgDoctorList?.length ?? 0;
+ regionList.registeredDoctorMap?[region] = regionData;
+ }
+
+ return regionList;
+ }
+}
\ No newline at end of file
diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
new file mode 100644
index 0000000..7bcd0a6
--- /dev/null
+++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
@@ -0,0 +1,66 @@
+import 'package:flutter/foundation.dart' show ChangeNotifier;
+
+enum AppointmentViaRegionState {
+ REGION_SELECTION,
+ TYPE_SELECTION,
+ HOSPITAL_SELECTION,
+ CLINIC_SELECTION,
+ DOCTOR_SELECTION
+}
+
+class AppointmentViaRegionViewmodel extends ChangeNotifier {
+ String? selectedRegionId;
+ String? selectedFacilityType;
+ AppointmentViaRegionState bottomSheetState =
+ AppointmentViaRegionState.REGION_SELECTION;
+
+ void setSelectedRegionId(String? regionId) {
+ selectedRegionId = regionId;
+ notifyListeners();
+ }
+
+ void setFacility(String? facility) {
+ selectedFacilityType = facility;
+ notifyListeners();
+ }
+
+ void setBottomSheetState(AppointmentViaRegionState state) {
+ bottomSheetState = state;
+ notifyListeners();
+ }
+
+ void handleBackPress() {
+ switch (bottomSheetState) {
+ case AppointmentViaRegionState.REGION_SELECTION:
+ // Do nothing or exit the bottom sheet
+ break;
+ case AppointmentViaRegionState.TYPE_SELECTION:
+ setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
+ setSelectedRegionId(null);
+ break;
+ case AppointmentViaRegionState.HOSPITAL_SELECTION:
+ setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ break;
+
+ // case AppointmentViaRegionState.HOSPITAL_SELECTION:
+ // case AppointmentViaRegionState.CLINIC_SELECTION:
+ // setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ // setFacility(null);
+ // break;
+ // case AppointmentViaRegionState.DOCTOR_SELECTION:
+ // if (selectedFacilityType == 'Hospital') {
+ // setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION);
+ // } else if (selectedFacilityType == 'Medical Center') {
+ // setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
+ // }
+ // break;
+ default:
+ }
+ }
+
+ void flush() {
+ setSelectedRegionId(null);
+ setFacility(null);
+ setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
+ }
+}
diff --git a/lib/features/my_appointments/models/facility_selection.dart b/lib/features/my_appointments/models/facility_selection.dart
new file mode 100644
index 0000000..b1f12de
--- /dev/null
+++ b/lib/features/my_appointments/models/facility_selection.dart
@@ -0,0 +1,8 @@
+enum FacilitySelection{
+ALL('ALL'),
+HMG('hmg'),
+HMC('hmc');
+
+final String value;
+const FacilitySelection(this.value);
+}
\ No newline at end of file
diff --git a/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart
new file mode 100644
index 0000000..c3155e1
--- /dev/null
+++ b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart
@@ -0,0 +1,274 @@
+
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
+
+class DoctorList {
+ int? clinicID;
+ dynamic appointmentNo;
+ String? clinicName;
+ String? doctorTitle;
+ num? iD;
+ String? name;
+ int? projectID;
+ String? projectName;
+ int? actualDoctorRate;
+ num? clinicRoomNo;
+ dynamic date;
+ dynamic appointmentDate;
+ dynamic dayName;
+ int? doctorID;
+ String? doctorImageURL;
+ dynamic doctorProfile;
+ dynamic doctorProfileInfo;
+ int? doctorRate;
+ num? gender;
+ String? genderDescription;
+ bool? isAppointmentAllowed;
+ bool? isDoctorAllowVedioCall;
+ bool? isDoctorDummy;
+ bool? isLiveCare;
+ bool? isLiveCareClinic;
+ bool? isDoctorHasPrePostImages;
+ String? latitude;
+ String? longitude;
+ String? nationalityFlagURL;
+ String? nationalityID;
+ String? nationalityName;
+ dynamic nearestFreeSlot;
+ int? noOfPatientsRate;
+ num? originalClinicID;
+ num? personRate;
+ dynamic projectDistanceInKiloMeters;
+ String? qR;
+ dynamic qRString;
+ num? rateNumber;
+ dynamic serviceID;
+ String? setupID;
+ List? speciality;
+ List? specialityN;
+ dynamic workingHours;
+ dynamic decimalDoctorRate;
+ String? projectBottomName;
+ String? projectTopName;
+ bool? isHMC;
+ String? region;
+ String? regionArabic;
+ String? regionEnglish;
+ String? regionID;
+
+ DoctorList(
+ {this.clinicID,
+ this.appointmentNo,
+ this.clinicName,
+ this.doctorTitle,
+ this.iD,
+ this.name,
+ this.projectID,
+ this.projectName,
+ this.actualDoctorRate,
+ this.clinicRoomNo,
+ this.date,
+ this.appointmentDate,
+ this.dayName,
+ this.doctorID,
+ this.doctorImageURL,
+ this.doctorProfile,
+ this.doctorProfileInfo,
+ this.doctorRate,
+ this.gender,
+ this.genderDescription,
+ this.isAppointmentAllowed,
+ this.isDoctorAllowVedioCall,
+ this.isDoctorDummy,
+ this.isLiveCare,
+ this.isLiveCareClinic,
+ this.isDoctorHasPrePostImages,
+ this.latitude,
+ this.longitude,
+ this.nationalityFlagURL,
+ this.nationalityID,
+ this.nationalityName,
+ this.nearestFreeSlot,
+ this.noOfPatientsRate,
+ this.originalClinicID,
+ this.personRate,
+ this.projectDistanceInKiloMeters,
+ this.qR,
+ this.qRString,
+ this.rateNumber,
+ this.serviceID,
+ this.setupID,
+ this.speciality,
+ this.specialityN,
+ this.workingHours,
+ this.decimalDoctorRate,
+ this.projectBottomName,
+ this.projectTopName,
+ this.isHMC,
+ this.region,
+ this.regionArabic,
+ this.regionEnglish,
+ this.regionID,
+ });
+
+ DoctorList.fromJson(
+ Map json,
+ ) {
+ clinicID = json['ClinicID'];
+ appointmentNo = json['AppointmentNo'];
+ clinicName = json['ClinicName'];
+ doctorTitle = json['DoctorTitle'];
+ iD = json['ID'];
+ name = json['DoctorName'] ?? json['Name'];
+ projectID = json['ProjectID'];
+ projectName = json['ProjectName'];
+ actualDoctorRate = json['ActualDoctorRate'];
+ clinicRoomNo = json['ClinicRoomNo'];
+ date = json['Date'];
+ appointmentDate = json['AppointmentDate'];
+ dayName = json['DayName'];
+ doctorID = json['DoctorID'];
+ doctorImageURL = json['DoctorImageURL'];
+ doctorProfile = json['DoctorProfile'];
+ doctorProfileInfo = json['DoctorProfileInfo'];
+ doctorRate = json['DoctorRate'];
+ gender = json['Gender'];
+ genderDescription = json['GenderDescription'];
+ isAppointmentAllowed = json['IsAppointmentAllowed'];
+ isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall'];
+ isDoctorDummy = json['IsDoctorDummy'];
+ isLiveCare = json['IsLiveCare'];
+ isLiveCareClinic = json['IsLiveCareClinic'];
+ isDoctorHasPrePostImages = json['IsDoctorHasPrePostImages'];
+ latitude = json['Latitude'];
+ longitude = json['Longitude'];
+ nationalityFlagURL = json['NationalityFlagURL'];
+ nationalityID = json['NationalityID'];
+ nationalityName = json['NationalityName'];
+ nearestFreeSlot = json['NearestFreeSlot'];
+ noOfPatientsRate = json['NoOfPatientsRate'];
+ originalClinicID = json['OriginalClinicID'];
+ personRate = json['PersonRate'];
+ projectDistanceInKiloMeters = json['ProjectDistanceInKiloMeters'];
+ qR = json['QR'];
+ qRString = json['QRString'];
+ rateNumber = json['RateNumber'];
+ serviceID = json['ServiceID'];
+ setupID = json['SetupID'];
+ if (json.containsKey('Speciality') && json['Speciality'] != null) speciality = json['Speciality'].cast();
+ if (json.containsKey('SpecialityN') && json['SpecialityN'] != null) specialityN = json['SpecialityN'].cast();
+ workingHours = json['WorkingHours'];
+ decimalDoctorRate = json['DecimalDoctorRate'];
+ projectBottomName = json['ProjectNameBottom'];
+ projectTopName = json['ProjectNameTop'];
+ this.isHMC = json["IsHMC"];
+ this.regionArabic = json['RegionNameN'];
+ this.regionEnglish = json['RegionName'];
+ }
+
+ String? getRegionName(bool isArabic) {
+ if (isArabic) {
+ return regionArabic;
+ }
+ return regionEnglish;
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['ClinicID'] = this.clinicID;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['ClinicName'] = this.clinicName;
+ data['DoctorTitle'] = this.doctorTitle;
+ data['ID'] = this.iD;
+ data['Name'] = this.name;
+ data['ProjectID'] = this.projectID;
+ data['ProjectName'] = this.projectName;
+ data['ActualDoctorRate'] = this.actualDoctorRate;
+ data['ClinicRoomNo'] = this.clinicRoomNo;
+ data['Date'] = this.date;
+ data['DayName'] = this.dayName;
+ data['DoctorID'] = this.doctorID;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ data['DoctorProfile'] = this.doctorProfile;
+ data['DoctorProfileInfo'] = this.doctorProfileInfo;
+ data['DoctorRate'] = this.doctorRate;
+ data['Gender'] = this.gender;
+ data['GenderDescription'] = this.genderDescription;
+ data['IsAppointmentAllowed'] = this.isAppointmentAllowed;
+ data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall;
+ data['IsDoctorDummy'] = this.isDoctorDummy;
+ data['IsLiveCare'] = this.isLiveCare;
+ data['IsLiveCareClinic'] = this.isLiveCareClinic;
+ data['IsDoctorHasPrePostImages'] = this.isDoctorHasPrePostImages;
+ data['Latitude'] = this.latitude;
+ data['Longitude'] = this.longitude;
+ data['NationalityFlagURL'] = this.nationalityFlagURL;
+ data['NationalityID'] = this.nationalityID;
+ data['NationalityName'] = this.nationalityName;
+ data['NearestFreeSlot'] = this.nearestFreeSlot;
+ data['NoOfPatientsRate'] = this.noOfPatientsRate;
+ data['OriginalClinicID'] = this.originalClinicID;
+ data['PersonRate'] = this.personRate;
+ data['ProjectDistanceInKiloMeters'] = this.projectDistanceInKiloMeters;
+ data['QR'] = this.qR;
+ data['QRString'] = this.qRString;
+ data['RateNumber'] = this.rateNumber;
+ data['ServiceID'] = this.serviceID;
+ data['SetupID'] = this.setupID;
+ data['Speciality'] = this.speciality;
+ data['SpecialityN'] = this.specialityN;
+ data['WorkingHours'] = this.workingHours;
+ data['DecimalDoctorRate'] = this.decimalDoctorRate;
+ return data;
+ }
+
+ String getProjectCompleteName(){
+ return "${this.projectTopName} ${this.projectBottomName}";
+ }
+
+ String getProjectCompleteNameWithLocale({bool isArabic = false}) {
+ if (isArabic) {
+ return "${this.projectBottomName} ${this.projectTopName}";
+ }
+ return "${this.projectTopName} ${this.projectBottomName}";
+ }
+}
+
+class PatientDoctorAppointmentList {
+ String? filterName = "";
+ String? distanceInKMs = "";
+ List? patientDoctorAppointmentList = [];
+ String? projectTopName = "";
+ String? projectBottomName = "";
+ bool? isHMC;
+ List hospitalList = [];
+
+ PatientDoctorAppointmentList(
+ {this.filterName,
+ this.distanceInKMs,
+ this.projectTopName,
+ this.projectBottomName,
+ DoctorList? patientDoctorAppointment,
+ HospitalsModel? model,
+ this.isHMC = false}) {
+ if (model != null) {
+ hospitalList.add(model);
+ }
+ if (patientDoctorAppointment != null) {
+ patientDoctorAppointmentList!.add(patientDoctorAppointment!);
+ }
+ }
+}
+
+class PatientDoctorAppointmentListByRegion {
+ List? hmgDoctorList = [];
+ List? hmcDoctorList = [];
+ int hmcSize = 0;
+ int hmgSize = 0;
+ num distance = double.infinity;
+ num hmgDistance = double.infinity;
+ num hmcDistance = double.infinity;
+}
+
+class RegionList {
+ Map? registeredDoctorMap = {};
+}
\ No newline at end of file
diff --git a/lib/features/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart
new file mode 100644
index 0000000..342fcea
--- /dev/null
+++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart
@@ -0,0 +1,104 @@
+class HospitalsModel {
+ String? desciption;
+ dynamic desciptionN;
+ dynamic iD;
+ String? legalName;
+ String? legalNameN;
+ String? name;
+ dynamic nameN;
+ String? phoneNumber;
+ String? setupID;
+ dynamic distanceInKilometers;
+ bool? isActive;
+ String? latitude;
+ String? longitude;
+ dynamic mainProjectID;
+ bool? projectOutSA;
+ bool? usingInDoctorApp;
+ bool? isHMC;
+ String? region;
+ String? regionArabic;
+ String? regionEnglish;
+ String? regionID;
+
+ HospitalsModel({
+ this.desciption,
+ this.desciptionN,
+ this.iD,
+ this.legalName,
+ this.legalNameN,
+ this.name,
+ this.nameN,
+ this.phoneNumber,
+ this.setupID,
+ this.distanceInKilometers,
+ this.isActive,
+ this.latitude,
+ this.longitude,
+ this.mainProjectID,
+ this.projectOutSA,
+ this.usingInDoctorApp,
+ this.isHMC,
+ this.region,
+ this.regionArabic,
+ this.regionEnglish,
+ this.regionID,
+ });
+
+ HospitalsModel.fromJson(Map json) {
+ desciption = json['Desciption'];
+ desciptionN = json['DesciptionN'];
+ iD = json['ID'];
+ legalName = json['LegalName'];
+ legalNameN = json['LegalNameN'];
+ name = json['Name'];
+ nameN = json['NameN'];
+ phoneNumber = json['PhoneNumber'];
+ setupID = json['SetupID'];
+ distanceInKilometers = json['DistanceInKilometers'];
+ isActive = json['IsActive'];
+ latitude = json['Latitude'];
+ longitude = json['Longitude'];
+ mainProjectID = json['MainProjectID'];
+ projectOutSA = json['ProjectOutSA'];
+ usingInDoctorApp = json['UsingInDoctorApp'];
+ this.isHMC = json["IsHMC"];
+ this.regionArabic = json['RegionNameN'];
+ this.regionEnglish = json['RegionName'];
+ }
+
+ String? getRegionName(bool isArabic) {
+ if (isArabic) {
+ return regionArabic;
+ }
+ return regionEnglish;
+ }
+
+ String? getName(bool isArabic) {
+ if (isArabic) {
+ return "$nameN";
+ }
+ return name;
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['Desciption'] = this.desciption;
+ data['DesciptionN'] = this.desciptionN;
+ data['ID'] = this.iD;
+ data['LegalName'] = this.legalName;
+ data['LegalNameN'] = this.legalNameN;
+ data['Name'] = this.name;
+ data['NameN'] = this.nameN;
+ data['PhoneNumber'] = this.phoneNumber;
+ data['SetupID'] = this.setupID;
+ data['DistanceInKilometers'] = this.distanceInKilometers;
+ data['IsActive'] = this.isActive;
+ data['Latitude'] = this.latitude;
+ data['Longitude'] = this.longitude;
+ data['MainProjectID'] = this.mainProjectID;
+ data['ProjectOutSA'] = this.projectOutSA;
+ data['UsingInDoctorApp'] = this.usingInDoctorApp;
+ return data;
+ }
+}
diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart
index 2035f30..44c3954 100644
--- a/lib/features/my_appointments/my_appointments_repo.dart
+++ b/lib/features/my_appointments/my_appointments_repo.dart
@@ -3,6 +3,8 @@ 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/my_appointments/models/resp_models/hospital_model.dart'
+ show HospitalsModel;
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/models/resp_models/patient_appointment_share_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@@ -36,6 +38,9 @@ abstract class MyAppointmentsRepo {
Future>>> getPatientAppointmentsForTimeLine();
Future>>> getPatientDoctorsList();
+
+ Future>>>
+ getProjectList();
}
class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@@ -491,4 +496,47 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
+
+ @override
+ Future>>>
+ getProjectList() async {
+ Map request = {};
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_PROJECT_LIST,
+ body: request,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['ListProject'];
+
+ final appointmentsList = list
+ .map((item) =>
+ HospitalsModel.fromJson(item as Map))
+ .toList()
+ .cast();
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: appointmentsList,
+ );
+ } 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/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index c6d3499..ce303de 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -1,38 +1,60 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst;
+import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
+import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'
+ show RegionList, PatientDoctorAppointmentListByRegion;
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/models/resp_models/patient_appointment_share_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+import 'package:location/location.dart' show Location;
+
+import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper;
+
class MyAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0;
MyAppointmentsRepo myAppointmentsRepo;
ErrorHandlerService errorHandlerService;
+ AppState appState;
bool isMyAppointmentsLoading = false;
bool isAppointmentPatientShareLoading = false;
bool isTimeLineAppointmentsLoading = false;
bool isPatientMyDoctorsLoading = false;
- List patientAppointmentsHistoryList = [];
+ List patientAppointmentsHistoryList =
+ [];
- List patientUpcomingAppointmentsHistoryList = [];
- List patientArrivedAppointmentsHistoryList = [];
+ List
+ patientUpcomingAppointmentsHistoryList = [];
+ List
+ patientArrivedAppointmentsHistoryList = [];
- List patientTimelineAppointmentsList = [];
+ List patientTimelineAppointmentsList =
+ [];
List patientMyDoctorsList = [];
PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel;
- MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService});
+ RegionList? hospitalList;
+ RegionList? filteredHospitalList;
+ FacilitySelection currentlySelectedFacility = FacilitySelection.ALL;
+ bool isRegionListLoading = false;
+
+ MyAppointmentsViewModel(
+ {required this.myAppointmentsRepo, required this.errorHandlerService,required this.appState});
void onTabChange(int index) {
selectedTabIndex = index;
notifyListeners();
}
+
initAppointmentsViewModel() {
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
@@ -43,6 +65,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
isAppointmentPatientShareLoading = true;
isTimeLineAppointmentsLoading = true;
isPatientMyDoctorsLoading = true;
+ isRegionListLoading = true;
notifyListeners();
}
@@ -66,7 +89,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
- setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) {
+ setAppointmentReminder(
+ bool value, PatientAppointmentHistoryResponseModel item) {
int index = patientAppointmentsHistoryList.indexOf(item);
if (index != -1) {
patientAppointmentsHistoryList[index].hasReminder = value;
@@ -74,12 +98,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
}
- Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments);
- final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true);
+ Future getPatientAppointments(
+ bool isActiveAppointment, bool isArrivedAppointments,
+ {Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.getPatientAppointments(
+ isActiveAppointment: isActiveAppointment,
+ isArrivedAppointments: isArrivedAppointments);
+ final resultArrived = await myAppointmentsRepo.getPatientAppointments(
+ isActiveAppointment: false, isArrivedAppointments: true);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -95,7 +125,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
resultArrived.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -110,19 +141,27 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
- patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList);
- patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
+ patientAppointmentsHistoryList
+ .addAll(patientUpcomingAppointmentsHistoryList);
+ patientAppointmentsHistoryList
+ .addAll(patientArrivedAppointmentsHistoryList);
- print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
- print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
+ print(
+ 'Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
+ print(
+ 'Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
}
- Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo);
+ Future getPatientShareAppointment(
+ int projectID, int clinicID, String appointmentNo,
+ {Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.getPatientShareAppointment(
+ projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -139,11 +178,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
Future addAdvanceNumberRequest(
- {required String advanceNumber, required String paymentReference, required String appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo);
+ {required String advanceNumber,
+ required String paymentReference,
+ required String appointmentNo,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.addAdvanceNumberRequest(
+ advanceNumber: advanceNumber,
+ paymentReference: paymentReference,
+ appointmentNo: appointmentNo);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -158,11 +205,21 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
Future generateAppointmentQR(
- {required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp, Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.generateAppointmentQR(clinicID: clinicID, projectID: projectID, appointmentNo: appointmentNo, isFollowUp: isFollowUp);
+ {required int clinicID,
+ required int projectID,
+ required String appointmentNo,
+ required int isFollowUp,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.generateAppointmentQR(
+ clinicID: clinicID,
+ projectID: projectID,
+ appointmentNo: appointmentNo,
+ isFollowUp: isFollowUp);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -176,11 +233,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
- Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel);
+ Future cancelAppointment(
+ {required PatientAppointmentHistoryResponseModel
+ patientAppointmentHistoryResponseModel,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.cancelAppointment(
+ patientAppointmentHistoryResponseModel:
+ patientAppointmentHistoryResponseModel);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
@@ -195,11 +259,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
- Future confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
- final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel);
+ Future confirmAppointment(
+ {required PatientAppointmentHistoryResponseModel
+ patientAppointmentHistoryResponseModel,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError}) async {
+ final result = await myAppointmentsRepo.confirmAppointment(
+ patientAppointmentHistoryResponseModel:
+ patientAppointmentHistoryResponseModel);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
@@ -236,7 +307,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientType: patientType);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -251,15 +323,21 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
Future sendCheckInNfcRequest(
- {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel,
+ {required PatientAppointmentHistoryResponseModel
+ patientAppointmentHistoryResponseModel,
required String scannedCode,
required int checkInType,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
- final result = await myAppointmentsRepo.sendCheckInNfcRequest(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType);
+ final result = await myAppointmentsRepo.sendCheckInNfcRequest(
+ patientAppointmentHistoryResponseModel:
+ patientAppointmentHistoryResponseModel,
+ scannedCode: scannedCode,
+ checkInType: checkInType);
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
@@ -274,11 +352,13 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
- Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ Future getPatientMyDoctors(
+ {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getPatientDoctorsList();
result.fold(
- (failure) async => await errorHandlerService.handleError(failure: failure),
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@@ -293,4 +373,84 @@ class MyAppointmentsViewModel extends ChangeNotifier {
},
);
}
+
+ Future getRegionMappedProjectList() async {
+ if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){
+ filteredHospitalList = hospitalList;
+ return;
+ }
+ isRegionListLoading = true;
+ notifyListeners();
+ final result = await myAppointmentsRepo.getProjectList();
+
+ result.fold(
+ (failure) async =>
+ await errorHandlerService.handleError(failure: failure),
+ (apiResponse) async {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ var projectList = apiResponse.data!;
+ hospitalList = await DoctorMapper.getMappedHospitals(projectList,
+ isArabic: false);
+ var lat = await Utils.getNumFromPrefs(CacheConst.userLat);
+
+ var lng = await Utils.getNumFromPrefs(CacheConst.userLong);
+ var isLocationEnabled = (lat != 0) && (lng != 0);
+ hospitalList =
+ await DoctorMapper.sortList(isLocationEnabled, hospitalList!);
+
+ isRegionListLoading = false;
+ filteredHospitalList = hospitalList;
+ notifyListeners();
+ }
+ },
+ );
+ }
+
+ void setSelectedFacility(FacilitySelection selection) {
+ currentlySelectedFacility = selection;
+ notifyListeners();
+ }
+
+ void filterHospitalListByString(String? value, String? selectedRegionId, bool isHMG) {
+ if(value ==null || value.isEmpty){
+ filteredHospitalList = hospitalList;
+ } else {
+ filteredHospitalList = RegionList();
+
+ var list = isHMG
+ ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList
+ : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList;
+
+ if(list != null && list.isEmpty){ notifyListeners(); return;}
+
+ var filteredList = list!.where((element) =>
+ element.filterName!.toLowerCase().contains(value.toLowerCase())
+ ).toList();
+ var regionData = PatientDoctorAppointmentListByRegion();
+ if(isHMG){
+ regionData.hmgDoctorList = filteredList;
+ regionData.hmgSize = filteredList.length;
+ } else {
+ regionData.hmcDoctorList = filteredList;
+ regionData.hmcSize = filteredList.length;
+ }
+
+ filteredHospitalList?.registeredDoctorMap = {
+ selectedRegionId! : regionData
+ };
+ }
+ notifyListeners();
+ }
+
+ Future isLocationEnabled() async{
+ return await Location().serviceEnabled();
+ }
+
+ bool getLocationStatus() {
+ bool isLocationAvaiable = false;
+ isLocationEnabled().then((value) => isLocationAvaiable = value);
+ return isLocationAvaiable;
+ }
}
diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart
index 9a341a2..4e27533 100644
--- a/lib/generated/locale_keys.g.dart
+++ b/lib/generated/locale_keys.g.dart
@@ -815,6 +815,13 @@ abstract class LocaleKeys {
static const ready = 'ready';
static const enterValidNationalId = 'enterValidNationalId';
static const enterValidPhoneNumber = 'enterValidPhoneNumber';
+ static const medicalCentersWithCount = 'medicalCentersWithCount';
+ static const medicalCenters = 'medicalCenters';
+ static const hospitalsWithCount = 'hospitalsWithCount';
+ static const selectRegion = 'selectRegion';
+ static const selectFacility = 'selectFacility';
+ static const selectFacilitiesSubTitle = 'selectFacilitiesSubTitle';
+ static const selectHospitalSubTitle = 'selectHospitalSubTitle';
static const news = 'news';
}
diff --git a/lib/main.dart b/lib/main.dart
index e727d19..02f8dd1 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_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_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';
@@ -110,6 +111,7 @@ void main() async {
create: (_) => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
+ appState: getIt(),
),
),
ChangeNotifierProvider(
@@ -143,6 +145,8 @@ void main() async {
localAuthService: getIt(),
),
),
+ ChangeNotifierProvider(
+ create: (_) => AppointmentViaRegionViewmodel())
], child: MyApp()),
),
);
diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart
new file mode 100644
index 0000000..1660146
--- /dev/null
+++ b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart
@@ -0,0 +1,88 @@
+import 'package:flutter/material.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';
+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/theme/colors.dart' show AppColors;
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+
+class FacilitySelectionItem extends StatelessWidget {
+ final String svgPath;
+ final String title;
+ final String subTitle;
+
+ const FacilitySelectionItem(
+ {super.key,
+ required this.svgPath,
+ required this.subTitle,
+ required this.title});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: false,
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: svgPath,
+ width: 32,
+ height: 32,
+ fit: BoxFit.contain,
+ ),
+ SizedBox(height: 16,),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ info,
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.forward_arrow_icon,
+ iconColor: AppColors.blackColor,
+ width: 18,
+ height: 13,
+ fit: BoxFit.contain,
+ ),
+ ],
+ )
+ ],
+ ).paddingAll(16.h),
+ );
+ }
+
+
+ Widget get info => Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ title,
+ style: TextStyle(
+ fontSize: 16.h,
+ fontWeight: FontWeight.w600,
+ color: AppColors.blackColor,
+ ),
+ ),
+ ),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ subTitle,
+ style: TextStyle(
+ fontSize: 12.h,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ),
+ ],
+ );
+}
diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart
new file mode 100644
index 0000000..64af81e
--- /dev/null
+++ b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart
@@ -0,0 +1,79 @@
+import 'package:easy_localization/easy_localization.dart'
+ show tr, StringTranslateExtension;
+import 'package:flutter/cupertino.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/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'
+ show MyAppointmentsViewModel;
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:provider/provider.dart' show Provider;
+
+class FacilityTypeSelectionWidget extends StatelessWidget {
+ late MyAppointmentsViewModel myAppointmentsViewModel;
+ late AppointmentViaRegionViewmodel regionalViewModel;
+ final String selectedRegion;
+
+ FacilityTypeSelectionWidget({super.key, required this.selectedRegion});
+
+ @override
+ Widget build(BuildContext context) {
+ myAppointmentsViewModel = Provider.of(context);
+ regionalViewModel = Provider.of(context);
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ LocaleKeys.selectFacility.tr(),
+ style: TextStyle(
+ fontSize: 21,
+ fontWeight: FontWeight.w600,
+ color: AppColors.blackColor,
+ ),
+ ),
+ Text(
+ LocaleKeys.selectFacilitiesSubTitle,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ FacilitySelectionItem(
+ svgPath: AppAssets.hmg,
+ title: "HMG".needTranslation,
+ subTitle: LocaleKeys.hospitalsWithCount.tr(namedArgs: {
+ 'count':
+ "${myAppointmentsViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmgSize ?? 0}"
+ }),
+ ).onPress(
+ () {
+ regionalViewModel.setFacility(FacilitySelection.HMG.name);
+ regionalViewModel.setBottomSheetState(
+ AppointmentViaRegionState.HOSPITAL_SELECTION);
+ },
+ ),
+ SizedBox(height: 16.h),
+ FacilitySelectionItem(
+ svgPath: AppAssets.hmc,
+ title: "HMC".needTranslation,
+ subTitle: LocaleKeys.medicalCentersWithCount.tr(namedArgs: {
+ 'count':
+ "${myAppointmentsViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}"
+ })).onPress(
+ () {
+ regionalViewModel.setFacility(FacilitySelection.HMC.name);
+ regionalViewModel.setBottomSheetState(
+ AppointmentViaRegionState.HOSPITAL_SELECTION);
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart
new file mode 100644
index 0000000..803d75c
--- /dev/null
+++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart
@@ -0,0 +1,109 @@
+import 'package:easy_localization/easy_localization.dart'
+ show tr, StringTranslateExtension;
+import 'package:flutter/material.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/features/my_appointments/appointment_via_region_viewmodel.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.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/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart';
+import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
+import 'package:hmg_patient_app_new/widgets/input_widget.dart';
+import 'package:provider/provider.dart';
+
+class HospitalBottomSheetBody extends StatelessWidget {
+ late MyAppointmentsViewModel appointmentsViewModel;
+ late AppointmentViaRegionViewmodel regionalViewModel;
+ final TextEditingController searchText = TextEditingController();
+
+ HospitalBottomSheetBody({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ appointmentsViewModel = Provider.of(context);
+ regionalViewModel = Provider.of(context);
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ LocaleKeys.selectHospital.tr(),
+ style: TextStyle(
+ fontSize: 21,
+ fontWeight: FontWeight.w600,
+ color: AppColors.blackColor,
+ ),
+ ),
+ Text(
+ LocaleKeys.selectHospitalSubTitle.tr(),
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ TextInputWidget(
+ labelText: LocaleKeys.search.tr(),
+ hintText: "Search Hospital".tr(),
+ controller: searchText,
+ onChange: (value) {
+ appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType ==
+ FacilitySelection.HMG.name);
+ },
+ isEnable: true,
+ prefix: null,
+ autoFocus: false,
+ isBorderAllowed: false,
+ keyboardType: TextInputType.text,
+ isAllowLeadingIcon: true,
+ selectionType: SelectionTypeEnum.search,
+ padding: EdgeInsets.symmetric(
+ vertical: ResponsiveExtension(10).h,
+ horizontal: ResponsiveExtension(15).h,
+ ),
+ ),
+ SizedBox(height: 24.h),
+ // TypeSelectionWidget(
+ // hmcCount: "0",
+ // hmgCount: "0",
+ // ),
+ // SizedBox(height: 21.h),
+ SizedBox(
+ height: MediaQuery.sizeOf(context).height * .4,
+ child: ListView.separated(
+ itemBuilder: (_, index) => HospitalListItem(
+ hospitalData: regionalViewModel.selectedFacilityType ==
+ FacilitySelection.HMG.name
+ ? appointmentsViewModel
+ .filteredHospitalList!
+ .registeredDoctorMap![
+ regionalViewModel.selectedRegionId!]!
+ .hmgDoctorList![index]
+ : appointmentsViewModel
+ .filteredHospitalList
+ ?.registeredDoctorMap?[
+ regionalViewModel.selectedRegionId!]
+ ?.hmcDoctorList?[index],
+ isLocationEnabled: appointmentsViewModel.getLocationStatus(),
+ ),
+ separatorBuilder: (_, __) => SizedBox(
+ height: 16.h,
+ ),
+ itemCount: (regionalViewModel.selectedFacilityType ==
+ FacilitySelection.HMG.name
+ ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[
+ regionalViewModel.selectedRegionId]?.hmgDoctorList)
+ : (appointmentsViewModel
+ .filteredHospitalList
+ ?.registeredDoctorMap?[
+ regionalViewModel.selectedRegionId]?.hmcDoctorList))?.length ??
+ 0),
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart
new file mode 100644
index 0000000..41fb924
--- /dev/null
+++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart
@@ -0,0 +1,113 @@
+import 'package:flutter/material.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/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+
+class HospitalListItem extends StatelessWidget {
+ final PatientDoctorAppointmentList? hospitalData;
+ final bool isLocationEnabled;
+
+ late AppState appState;
+
+ HospitalListItem(
+ {super.key, required this.hospitalData, required this.isLocationEnabled});
+
+ @override
+ Widget build(BuildContext context) {
+ appState = getIt.get();
+ return DecoratedBox(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: false,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 8.h,
+ children: [hospitalName, distanceInfo],
+ ),
+ ),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.forward_arrow_icon,
+ iconColor: AppColors.blackColor,
+ width: 18,
+ height: 13,
+ fit: BoxFit.contain,
+ ),
+ ],
+ ).paddingSymmetrical(16.h, 16.h),
+ );
+ }
+
+ Widget get hospitalName => Row(
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: (hospitalData?.isHMC == true) ? AppAssets.hmc : AppAssets.hmg,
+ ).paddingOnly(right: 10),
+ Expanded(
+ child: Text(
+ hospitalData?.filterName ?? "",
+ style: TextStyle(
+ fontWeight: FontWeight.w600,
+ fontSize: 16,
+ color: AppColors.blackColor,
+ ),
+ ),
+ )
+ ],
+ );
+
+ Widget get distanceInfo => Row(
+
+ children: [
+ Visibility(
+ visible: (hospitalData?.distanceInKMs != "0"),
+ child:
+
+
+ AppCustomChipWidget(
+ labelText:
+ "${hospitalData?.distanceInKMs ?? ""} km".needTranslation,
+ deleteIcon: AppAssets.location_red,
+ deleteIconSize: Size(9, 12),
+ backgroundColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.errorColor,
+ ),
+
+
+ ),
+ Visibility(
+ visible: (hospitalData?.distanceInKMs == "0"),
+ child: Row(
+ children: [
+ AppCustomChipWidget(
+ labelText: "Distance not available".needTranslation,
+ textColor: AppColors.blackColor,
+ ),
+ SizedBox(width: 8.h,)
+
+ ],
+ )),
+ Visibility(
+ visible: !isLocationEnabled,
+ child: AppCustomChipWidget(
+ labelText: "Location turned off".needTranslation,
+ deleteIcon: AppAssets.location_unavailable,
+ deleteIconSize: Size(9, 12),
+ textColor: AppColors.blackColor,
+ )),
+ ],
+ );
+}
diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart
new file mode 100644
index 0000000..8f09e1a
--- /dev/null
+++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.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/my_appointments/models/facility_selection.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:provider/provider.dart' show Consumer;
+
+class TypeSelectionWidget extends StatelessWidget {
+ final String hmcCount;
+ final String hmgCount;
+
+ const TypeSelectionWidget(
+ {super.key, required this.hmcCount, required this.hmgCount});
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (_, data, __) => Row(
+ spacing: 8,
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ AppCustomChipWidget(
+ labelText: "All Facilities".needTranslation,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: data.currentlySelectedFacility == FacilitySelection.ALL
+ ? AppColors.errorColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ backgroundColor:
+ data.currentlySelectedFacility == FacilitySelection.ALL
+ ? AppColors.secondaryLightRedColor
+ : AppColors.whiteColor,
+ textColor: data.currentlySelectedFacility == FacilitySelection.ALL
+ ? AppColors.errorColor
+ : AppColors.blackColor,
+ ).onPress((){
+ data.setSelectedFacility(FacilitySelection.ALL);
+ }),
+ AppCustomChipWidget(
+ icon: AppAssets.hmg,
+ iconHasColor: false,
+ labelText: "Hospitals".needTranslation,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: data.currentlySelectedFacility == FacilitySelection.HMG
+ ? AppColors.errorColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ backgroundColor:
+ data.currentlySelectedFacility == FacilitySelection.HMG
+ ? AppColors.secondaryLightRedColor
+ : AppColors.whiteColor,
+ textColor: data.currentlySelectedFacility == FacilitySelection.HMG
+ ? AppColors.errorColor
+ : AppColors.blackColor,
+ ).onPress((){
+ data.setSelectedFacility(FacilitySelection.HMG);
+ }),
+ AppCustomChipWidget(
+ icon: AppAssets.hmc,
+ iconHasColor: false,
+ labelText: "Medical Centers".needTranslation,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ color: data.currentlySelectedFacility == FacilitySelection.HMC
+ ? AppColors.errorColor
+ : AppColors.chipBorderColorOpacity20,
+ width: 1,
+ ),
+ borderRadius: BorderRadius.circular(10)),
+ backgroundColor:
+ data.currentlySelectedFacility == FacilitySelection.HMC
+ ? AppColors.secondaryLightRedColor
+ : AppColors.whiteColor,
+ textColor: data.currentlySelectedFacility == FacilitySelection.HMC
+ ? AppColors.errorColor
+ : AppColors.blackColor,
+ ).onPress((){
+ data.setSelectedFacility(FacilitySelection.HMC);
+ }),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart
new file mode 100644
index 0000000..ab8e21f
--- /dev/null
+++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart
@@ -0,0 +1,114 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.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';
+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/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+
+class RegionListItem extends StatelessWidget {
+ final String title;
+ final String hmcCount;
+ final String hmgCount;
+ final String subTitle;
+
+ const RegionListItem(
+ {super.key,
+ required this.title,
+ required this.subTitle,
+ required this.hmcCount,
+ required this.hmgCount});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: false,
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ header,
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ spacing: 8.h,
+ children: [
+ placesCountItem(
+ AppAssets.hmg, hmgCount, " ${LocaleKeys.hospital.tr()}"),
+ placesCountItem(AppAssets.hmc, hmcCount,
+ " ${LocaleKeys.medicalCenters.tr()}"),
+ ],
+ ),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.forward_arrow_icon,
+ iconColor: AppColors.blackColor,
+ width: 18,
+ height: 13,
+ fit: BoxFit.contain,
+ ),
+ ],
+ )
+ ],
+ ).paddingAll(16.h),
+ );
+ }
+
+ Widget placesCountItem(String svgPath, String count, String title) {
+ return AppCustomChipWidget(
+ iconSize: 14,
+ icon: svgPath,
+ iconHasColor: false,
+ richText: RichText(
+ text: TextSpan(
+ text: count,
+ style: TextStyle(
+ fontSize: 12.h,
+ fontWeight: FontWeight.w700,
+ color: AppColors.blackColor),
+ children: [
+ TextSpan(
+ text: title,
+ style: TextStyle(
+ fontSize: 12.h,
+ fontWeight: FontWeight.w500,
+ color: AppColors.blackColor))
+ ])),
+ );
+ }
+
+ Widget get header => Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ title,
+ style: TextStyle(
+ fontSize: 16.h,
+ fontWeight: FontWeight.w600,
+ color: AppColors.blackColor,
+ ),
+ ),
+ ),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ subTitle,
+ style: TextStyle(
+ fontSize: 12.h,
+ fontWeight: FontWeight.w500,
+ color: AppColors.greyTextColor,
+ ),
+ ),
+ ),
+ ],
+ );
+}
diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart
new file mode 100644
index 0000000..d68637b
--- /dev/null
+++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart
@@ -0,0 +1,86 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
+import 'package:hmg_patient_app_new/extensions/widget_extensions.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_view_model.dart'
+ show MyAppointmentsViewModel;
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart'
+ show RegionListItem;
+import 'package:provider/provider.dart';
+
+class RegionBottomSheetBody extends StatefulWidget {
+
+ const RegionBottomSheetBody({super.key});
+
+ @override
+ State createState() => _RegionBottomSheetBodyState();
+}
+
+class _RegionBottomSheetBodyState extends State {
+ late MyAppointmentsViewModel myAppointmentsViewModel;
+ late AppointmentViaRegionViewmodel regionalViewModel;
+
+ @override
+ void initState() {
+ scheduleMicrotask(() {
+ myAppointmentsViewModel.getRegionMappedProjectList();
+ });
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ myAppointmentsViewModel = Provider.of(context);
+ regionalViewModel = Provider.of(context);
+ return Consumer(
+ builder: (context, myAppointmentsVM, child) {
+ if (myAppointmentsVM.isRegionListLoading) {
+ return Container(
+ height: MediaQuery.of(context).size.height * 0.3,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
+ ),
+ child: Center(
+ child: Utils.getLoadingWidget(),
+ ),
+ );
+ } else {
+ return SizedBox(
+ height: MediaQuery.of(context).size.height * 0.5,
+ child: ListView.separated(
+ itemCount:
+ myAppointmentsVM.hospitalList?.registeredDoctorMap?.length ??
+ 0,
+ separatorBuilder: (_, __) {
+ return SizedBox(
+ height: 16.h,
+ );
+ },
+ itemBuilder: (_, index) {
+ String key = myAppointmentsVM
+ .hospitalList?.registeredDoctorMap?.keys
+ .toList()[index] ??
+ '';
+ return RegionListItem(
+ title: key,
+ subTitle: "",
+ hmcCount:
+ "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmcSize ?? 0}",
+ hmgCount:
+ "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}",
+ ).onPress(() {
+ regionalViewModel.setSelectedRegionId(key);
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ });
+ },
+ ),
+ );
+ }
+ },
+ );
+ }
+}
diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart
index 9533d6c..a6d6c5e 100644
--- a/lib/presentation/book_appointment/book_appointment_page.dart
+++ b/lib/presentation/book_appointment/book_appointment_page.dart
@@ -10,15 +10,24 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_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/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'
+ show RegionBottomSheetBody;
import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'
+ show showCommonBottomSheetWithoutHeight;
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
+import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
+
class BookAppointmentPage extends StatefulWidget {
const BookAppointmentPage({super.key});
@@ -28,6 +37,7 @@ class BookAppointmentPage extends StatefulWidget {
class _BookAppointmentPageState extends State {
late AppState appState;
+ late AppointmentViaRegionViewmodel regionalViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@override
@@ -42,6 +52,8 @@ class _BookAppointmentPageState extends State {
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of(context, listen: false);
appState = getIt.get();
+ regionalViewModel =
+ Provider.of(context, listen: true);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
@@ -166,7 +178,9 @@ class _BookAppointmentPageState extends State {
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
],
- ).onPress(() {}),
+ ).onPress(() {
+ openRegionListBottomSheet(context);
+ }),
],
),
),
@@ -178,4 +192,45 @@ class _BookAppointmentPageState extends State {
}
return Container();
}
+
+ void openRegionListBottomSheet(BuildContext context) {
+ // AppointmentViaRegionViewmodel? viewmodel = null;
+ showCommonBottomSheetWithoutHeight(context,
+ title: "",
+ titleWidget: Consumer(
+ builder: (_, data, __) => getTitle(data)),
+ isDismissible: false,
+ child: Consumer(builder: (_, data, __) {
+ return getRegionalSelectionWidget(data);
+ }), callBackFunc: () {
+ regionalViewModel.flush();
+ });
+ }
+
+ Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
+ if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
+ return RegionBottomSheetBody();
+ }
+ if(data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION){
+ return FacilityTypeSelectionWidget(selectedRegion: data.selectedRegionId??"",);
+ }
+ if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
+ return HospitalBottomSheetBody();
+ } else {
+ SizedBox.shrink();
+ }
+ return SizedBox.shrink();
+ }
+
+ getTitle(AppointmentViaRegionViewmodel data) {
+ if (data.selectedRegionId == null) {
+ return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600);
+ } else {
+ return Utils.buildSvgWithAssets(
+ icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E))
+ .onPress(() {
+ data.handleBackPress();
+ });
+ }
+ }
}
diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart
index 8f96e57..0c7da58 100644
--- a/lib/theme/colors.dart
+++ b/lib/theme/colors.dart
@@ -31,6 +31,7 @@ class AppColors {
static const Color textColor = Color(0xFF2E3039);
static const Color textColorLight = Color(0xFF5E5E5E);
static const Color borderOnlyColor = Color(0xFF2E3039);
+ static const Color chipBorderColorOpacity20 = Color(0x332E3039);
static const Color dividerColor = Color(0xFFD2D2D2);
static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039);
diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart
index 203cb86..3ece752 100644
--- a/lib/widgets/chip/app_custom_chip_widget.dart
+++ b/lib/widgets/chip/app_custom_chip_widget.dart
@@ -10,23 +10,38 @@ import 'package:smooth_corner/smooth_corner.dart';
class AppCustomChipWidget extends StatelessWidget {
AppCustomChipWidget({
super.key,
- required this.labelText,
+ this.labelText,
this.textColor = AppColors.textColor,
this.backgroundColor = AppColors.greyColor,
this.iconSize = 12,
this.icon = "",
this.iconColor = AppColors.textColor,
+ this.richText,
+ this.iconHasColor = true,
+ this.shape,
+ this.deleteIcon,
+ this.deleteIconSize = const Size(12, 12),
+ this.deleteIconColor = AppColors.textColor,
+ this.deleteIconHasColor = false,
});
- String? labelText;
- Color? textColor;
- Color? backgroundColor;
- num iconSize;
- String icon;
- Color iconColor;
+ final String? labelText;
+ final Widget? richText;
+ final Color? textColor;
+ final Color? backgroundColor;
+ final num iconSize;
+ final String icon;
+ final String? deleteIcon;
+ final Size? deleteIconSize;
+ final Color iconColor;
+ final Color? deleteIconColor;
+ final bool iconHasColor;
+ final bool deleteIconHasColor;
+ final OutlinedBorder? shape;
@override
Widget build(BuildContext context) {
+ print("detected icon: $deleteIcon");
return ChipTheme(
data: ChipThemeData(
padding: EdgeInsets.all(0.0),
@@ -41,18 +56,55 @@ class AppCustomChipWidget extends StatelessWidget {
),
child: icon.isNotEmpty
? Chip(
- avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor) : SizedBox.shrink(),
- label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor),
+ avatar: icon.isNotEmpty
+ ? Utils.buildSvgWithAssets(
+ icon: icon,
+ width: iconSize.h,
+ height: iconSize.h,
+ iconColor: iconHasColor ? iconColor : null)
+ : SizedBox.shrink(),
+ label: richText ??
+ labelText!.toText10(
+ weight: FontWeight.w500,
+ letterSpacing: -0.64,
+ color: textColor),
padding: EdgeInsets.all(0.0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- labelPadding: EdgeInsets.only(left: -4.h, right: 8.h),
+ labelPadding: EdgeInsets.only(
+ left: -4.h,
+ right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h),
backgroundColor: backgroundColor,
+ shape: shape,
+ deleteIcon: deleteIcon?.isNotEmpty == true
+ ? Utils.buildSvgWithAssets(
+ icon: deleteIcon!,
+ width: deleteIconSize!.width!.h,
+ height: deleteIconSize!.height.h,
+ iconColor: deleteIconHasColor ? deleteIconColor : null)
+ : null,
+ onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
)
: Chip(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor),
+ label: richText ??
+ labelText!.toText10(
+ weight: FontWeight.w500,
+ letterSpacing: -0.64,
+ color: textColor),
padding: EdgeInsets.all(0.0),
backgroundColor: backgroundColor,
+ shape: shape,
+ labelPadding: EdgeInsets.only(
+ left: 8.h,
+ right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h),
+ deleteIcon: deleteIcon?.isNotEmpty == true
+ ? Utils.buildSvgWithAssets(
+ icon: deleteIcon!,
+ width: deleteIconSize!.width.h,
+ height: deleteIconSize!.height.h,
+ iconColor: deleteIconHasColor ? deleteIconColor : null)
+ : null,
+ onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
),
);
}
diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart
index b88c922..576e923 100644
--- a/lib/widgets/common_bottom_sheet.dart
+++ b/lib/widgets/common_bottom_sheet.dart
@@ -109,9 +109,11 @@ void showCommonBottomSheetWithoutHeight(
required Widget child,
required VoidCallback callBackFunc,
String title = "",
- bool isCloseButtonVisible = true,
- bool isFullScreen = true,
-}) {
+
+ bool isCloseButtonVisible = true,
+ bool isFullScreen = true,
+ bool isDismissible = true,
+ Widget? titleWidget,}) {
showModalBottomSheet(
sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration
@@ -120,6 +122,7 @@ void showCommonBottomSheetWithoutHeight(
context: context,
isScrollControlled: true,
showDragHandle: false,
+ isDismissible: isDismissible,
backgroundColor: AppColors.bottomSheetBgColor,
builder: (BuildContext context) {
return SafeArea(
@@ -135,9 +138,10 @@ void showCommonBottomSheetWithoutHeight(
spacing: 16.h,
children: [
Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
- title.toText20(weight: FontWeight.w600).expanded,
+ titleWidget ?? title.toText20(weight: FontWeight.w600),
Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
Navigator.of(context).pop();
}),
diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart
index 03d7b5f..c11ae65 100644
--- a/lib/widgets/input_widget.dart
+++ b/lib/widgets/input_widget.dart
@@ -141,6 +141,7 @@ class TextInputWidget extends StatelessWidget {
),
),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
+ if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context),
],
),
),
@@ -250,4 +251,16 @@ class TextInputWidget extends StatelessWidget {
),
);
}
+
+ _buildTrailingIconForSearch(BuildContext context) {
+ final AppState appState = getIt.get();
+ return Container(
+ height: 40.h,
+ width: 40.h,
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.all(8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor),
+ child: Utils.buildSvgWithAssets(icon: AppAssets.search_icon),
+ );
+ }
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 055fb7b..228376d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -81,6 +81,8 @@ dependencies:
flutter_swiper_view: ^1.1.8
family_bottom_sheet: ^0.1.0
+ location: ^8.0.1
+
dev_dependencies:
flutter_test:
sdk: flutter