diff --git a/assets/images/png/bmi_image_1.png b/assets/images/png/bmi_image_1.png
new file mode 100644
index 0000000..db3a613
Binary files /dev/null and b/assets/images/png/bmi_image_1.png differ
diff --git a/assets/images/svg/blood_pressure.svg b/assets/images/svg/blood_pressure.svg
new file mode 100644
index 0000000..67badbe
--- /dev/null
+++ b/assets/images/svg/blood_pressure.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bmi_2.svg b/assets/images/svg/bmi_2.svg
new file mode 100644
index 0000000..38468d7
--- /dev/null
+++ b/assets/images/svg/bmi_2.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/heart_rate.svg b/assets/images/svg/heart_rate.svg
new file mode 100644
index 0000000..15c754f
--- /dev/null
+++ b/assets/images/svg/heart_rate.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/height_2.svg b/assets/images/svg/height_2.svg
new file mode 100644
index 0000000..a1c361a
--- /dev/null
+++ b/assets/images/svg/height_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/resp_rate.svg b/assets/images/svg/resp_rate.svg
new file mode 100644
index 0000000..7038793
--- /dev/null
+++ b/assets/images/svg/resp_rate.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/symptom_checker_icon.svg b/assets/images/svg/symptom_checker_icon.svg
new file mode 100644
index 0000000..e41c1dd
--- /dev/null
+++ b/assets/images/svg/symptom_checker_icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/images/svg/temperature.svg b/assets/images/svg/temperature.svg
new file mode 100644
index 0000000..14c7da4
--- /dev/null
+++ b/assets/images/svg/temperature.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/weight_2.svg b/assets/images/svg/weight_2.svg
new file mode 100644
index 0000000..c22441f
--- /dev/null
+++ b/assets/images/svg/weight_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 4d5535f..2fdc389 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -246,6 +246,7 @@ class AppAssets {
static const String rotateIcon = '$svgBasePath/rotate_icon.svg';
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
+ static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg';
// Water Monitor
static const String waterBottle = '$svgBasePath/water_bottle.svg';
@@ -284,6 +285,19 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
+
+ //vital sign
+
+ static const String heartRate = '$svgBasePath/heart_rate.svg';
+ static const String respRate = '$svgBasePath/resp_rate.svg';
+ static const String weightVital = '$svgBasePath/weight_2.svg';
+ static const String bmiVital = '$svgBasePath/bmi_2.svg';
+ static const String heightVital = '$svgBasePath/height_2.svg';
+ static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
+ static const String temperature = '$svgBasePath/temperature.svg';
+
+
+
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@@ -309,7 +323,7 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
-
+ static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 489af59..c6c5554 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -34,6 +34,8 @@ 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_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/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';
@@ -138,6 +140,7 @@ class AppDependencies {
getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt()));
// ViewModels
// Global/shared VMs → LazySingleton
@@ -271,5 +274,7 @@ class AppDependencies {
getIt.registerLazySingleton(() => HealthProvider());
getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
+
+ getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
}
}
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index a30763f..c5ae123 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -846,6 +846,17 @@ class Utils {
isHMC: hospital.isHMC);
}
+
+ static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) {
+ if (item == null) return null;
+ return HospitalsModel(
+ name: item.filterName,
+ nameN: item.filterName,
+ distanceInKilometers: item.distanceInKMs,
+ isHMC: item.isHMC,
+ );
+ }
+
static bool havePrivilege(int id) {
bool isHavePrivilege = false;
try {
diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart
index 46649fb..b393772 100644
--- a/lib/features/authentication/authentication_view_model.dart
+++ b/lib/features/authentication/authentication_view_model.dart
@@ -32,6 +32,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart';
+import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@@ -39,6 +40,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
@@ -565,7 +567,7 @@ class AuthenticationViewModel extends ChangeNotifier {
if (!_appState.getIsChildLoggedIn) {
await medicalVm.getFamilyFiles(status: 0);
await medicalVm.getAllPendingRecordsByResponseId();
- _navigationService.popUntilNamed(AppRoutes.landingScreen);
+ _navigationService.replaceAllRoutesAndNavigateToLanding();
}
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
@@ -675,7 +677,12 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future navigateToHomeScreen() async {
- _navigationService.pushAndReplace(AppRoutes.landingScreen);
+ Navigator.pushAndRemoveUntil(
+ _navigationService.navigatorKey.currentContext!,
+ CustomPageRoute(
+ page: LandingNavigation(),
+ ),
+ (r) => false);
}
Future navigateToOTPScreen(
diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart
index 37a813d..85e6018 100644
--- a/lib/features/hmg_services/hmg_services_repo.dart
+++ b/lib/features/hmg_services/hmg_services_repo.dart
@@ -935,8 +935,8 @@ class HmgServicesRepoImp implements HmgServicesRepo {
try {
List vitalSignList = [];
- if (response['PatientVitalSignList'] != null && response['PatientVitalSignList'] is List) {
- final vitalSignsList = response['PatientVitalSignList'] as List;
+ if (response['List_DoctorPatientVitalSign'] != null && response['List_DoctorPatientVitalSign'] is List) {
+ final vitalSignsList = response['List_DoctorPatientVitalSign'] as List;
for (var vitalSignJson in vitalSignsList) {
if (vitalSignJson is Map) {
diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart
index b620c6d..c55a11c 100644
--- a/lib/features/hmg_services/hmg_services_view_model.dart
+++ b/lib/features/hmg_services/hmg_services_view_model.dart
@@ -51,6 +51,19 @@ class HmgServicesViewModel extends ChangeNotifier {
HospitalsModel? selectedHospital;
List vitalSignList = [];
+ // Vital Sign PageView Controller
+ PageController _vitalSignPageController = PageController();
+ PageController get vitalSignPageController => _vitalSignPageController;
+
+ int _vitalSignCurrentPage = 0;
+ int get vitalSignCurrentPage => _vitalSignCurrentPage;
+
+ void setVitalSignCurrentPage(int page) {
+ _vitalSignCurrentPage = page;
+ notifyListeners();
+ }
+
+
// HHC specific lists
List hhcOrdersList = [];
List hhcServicesList = [];
@@ -896,4 +909,10 @@ class HmgServicesViewModel extends ChangeNotifier {
},
);
}
+
+ @override
+ void dispose() {
+ _vitalSignPageController.dispose();
+ super.dispose();
+ }
}
diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
new file mode 100644
index 0000000..45b0ab6
--- /dev/null
+++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
@@ -0,0 +1,102 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+/// UI-only helper model for Vital Sign cards.
+///
+/// Keeps presentation logic (chip colors, icon colors, simple status rules)
+/// in one place so it can be reused across multiple pages.
+class VitalSignUiModel {
+ final Color iconBg;
+ final Color iconFg;
+ final Color chipBg;
+ final Color chipFg;
+
+ const VitalSignUiModel({
+ required this.iconBg,
+ required this.iconFg,
+ required this.chipBg,
+ required this.chipFg,
+ });
+
+ /// Returns a color scheme for a card based on its [status] and [label].
+ ///
+ /// Rules (mirrors existing behavior in Medical File page):
+ /// - Height is always blue.
+ /// - High => red scheme.
+ /// - Low => yellow scheme.
+ /// - Otherwise => green scheme (Normal).
+ static VitalSignUiModel scheme({required String? status, required String label}) {
+ final s = (status ?? '').toLowerCase();
+ final l = label.toLowerCase();
+
+ // Height should always be blue.
+ if (l.contains('height')) {
+ return VitalSignUiModel(
+ iconBg: AppColors.infoColor.withValues(alpha: 0.12),
+ iconFg: AppColors.infoColor,
+ chipBg: AppColors.infoColor.withValues(alpha: 0.12),
+ chipFg: AppColors.infoColor,
+ );
+ }
+
+ if (s.contains('high')) {
+ return const VitalSignUiModel(
+ iconBg: AppColors.chipSecondaryLightRedColor,
+ iconFg: AppColors.primaryRedColor,
+ chipBg: AppColors.chipSecondaryLightRedColor,
+ chipFg: AppColors.primaryRedColor,
+ );
+ }
+
+ if (s.contains('low')) {
+ final Color yellowBg = AppColors.warningColor.withValues(alpha: 0.12);
+ return VitalSignUiModel(
+ iconBg: yellowBg,
+ iconFg: AppColors.warningColor,
+ chipBg: yellowBg,
+ chipFg: AppColors.warningColor,
+ );
+ }
+
+ // Normal (green)
+ final Color greenBg = AppColors.lightGreenColor;
+ return VitalSignUiModel(
+ iconBg: greenBg,
+ iconFg: AppColors.bgGreenColor,
+ chipBg: greenBg,
+ chipFg: AppColors.bgGreenColor,
+ );
+ }
+
+ /// Simple, user-friendly classification:
+ /// - Low: systolic < 90 OR diastolic < 60
+ /// - High: systolic >= 140 OR diastolic >= 90
+ /// - Normal: otherwise
+ /// Returns null if values are missing/unparseable.
+ static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
+ final int? s = toIntOrNull(systolic);
+ final int? d = toIntOrNull(diastolic);
+ if (s == null || d == null) return null;
+
+ if (s < 90 || d < 60) return 'Low';
+ if (s >= 140 || d >= 90) return 'High';
+ return 'Normal';
+ }
+
+ static int? toIntOrNull(dynamic v) {
+ if (v == null) return null;
+ if (v is int) return v;
+ if (v is double) return v.round();
+ return int.tryParse(v.toString());
+ }
+
+ static String bmiStatus(dynamic bmi) {
+ if (bmi == null) return 'N/A';
+ final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
+ if (bmiValue < 18.5) return 'Underweight';
+ if (bmiValue < 25) return 'Normal';
+ if (bmiValue < 30) return 'Overweight';
+ return 'High';
+ }
+}
+
diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
index f51f701..c5dcaf6 100644
--- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart
+++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
@@ -1,6 +1,10 @@
import 'package:flutter/foundation.dart' show ChangeNotifier;
+import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart' show AppState;
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+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';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
@@ -30,7 +34,14 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
AppointmentViaRegionState bottomSheetState =
AppointmentViaRegionState.REGION_SELECTION;
final AppState appState;
-
+ TextEditingController searchController = TextEditingController();
+ List? hospitalList;
+ List? hmgHospitalList;
+ List? hmcHospitalList;
+ List? displayList;
+ FacilitySelection selectedFacility = FacilitySelection.ALL;
+ int hmgCount = 0;
+ int hmcCount = 0;
RegionBottomSheetType regionBottomSheetType = RegionBottomSheetType.FOR_REGION;
AppointmentViaRegionViewmodel({required this.navigationService,required this.appState});
@@ -40,6 +51,35 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
notifyListeners();
}
+ void setDisplayListAndRegionHospitalList(PatientDoctorAppointmentListByRegion? registeredDoctorMap){
+ if(registeredDoctorMap == null) {
+ return;
+ }
+ selectedFacility = FacilitySelection.ALL;
+ hmcHospitalList = [];
+ hmgHospitalList = [];
+ hospitalList = [];
+ displayList = [];
+ for(var data in registeredDoctorMap.hmgDoctorList!){
+ hmgHospitalList?.add(data);
+
+ }
+
+ for(var data in registeredDoctorMap.hmcDoctorList!){
+ hmcHospitalList?.add(data);
+
+ }
+
+ hospitalList!.addAll(hmgHospitalList!);
+ hospitalList!.addAll(hmcHospitalList!);
+
+ hmcCount = registeredDoctorMap.hmcSize;
+ hmgCount = registeredDoctorMap.hmgSize;
+
+ getDisplayList();
+
+ }
+
void setFacility(String? facility) {
selectedFacilityType = facility;
notifyListeners();
@@ -71,7 +111,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
setSelectedRegionId(null);
break;
case AppointmentViaRegionState.HOSPITAL_SELECTION:
- setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
break;
default:
}
@@ -129,4 +169,48 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
),
);
}
+
+ searchHospitals(String query) {
+ if (query.isEmpty) {
+ getDisplayList();
+ return;
+ }
+ List? sourceList;
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ sourceList = hospitalList;
+ break;
+ case FacilitySelection.HMG:
+ sourceList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ sourceList = hmcHospitalList;
+ break;
+ }
+ displayList = sourceList?.where((hospital) => hospital.filterName != null && hospital.filterName!.toLowerCase().contains(query.toLowerCase())).toList();
+ notifyListeners();
+ }
+
+ getDisplayList() {
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ displayList = hospitalList;
+ break;
+ case FacilitySelection.HMG:
+ displayList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ displayList = hmcHospitalList;
+ break;
+ }
+ notifyListeners();
+ }
+
+
+ setSelectedFacility(FacilitySelection selection) {
+ selectedFacility = selection;
+ notifyListeners();
+ }
+
+
}
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index b4a25ee..9bd48ec 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -127,6 +127,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
initAppointmentsViewModel() {
if (isAppointmentDataToBeLoaded) {
+ // Default view is grouped by clinic on first open.
+ isAppointmentsSortByClinic = true;
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
@@ -269,6 +271,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
+ // Build grouped list immediately so the UI has data for the default (By Clinic) view.
+ _groupAppointmentsByClinicAndHospital();
+
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
@@ -280,6 +285,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
+ notifyListeners();
}
void getFiltersForSelectedAppointmentList(List filteredAppointmentList) {
diff --git a/lib/features/my_invoices/models/get_invoice_details_response_model.dart b/lib/features/my_invoices/models/get_invoice_details_response_model.dart
new file mode 100644
index 0000000..ef88623
--- /dev/null
+++ b/lib/features/my_invoices/models/get_invoice_details_response_model.dart
@@ -0,0 +1,489 @@
+class GetInvoiceDetailsResponseModel {
+ int? projectID;
+ int? doctorID;
+ num? grandTotal;
+ num? quantity;
+ num? total;
+ num? discount;
+ num? subTotal;
+ int? invoiceNo;
+ String? createdOn;
+ String? procedureID;
+ String? procedureName;
+ String? procedureNameN;
+ num? procedurePrice;
+ num? patientShare;
+ num? companyShare;
+ num? totalPatientShare;
+ num? totalCompanyShare;
+ num? totalShare;
+ num? discountAmount;
+ num? vATPercentage;
+ num? patientVATAmount;
+ num? companyVATAmount;
+ num? totalVATAmount;
+ num? price;
+ int? patientID;
+ String? patientIdentificationNo;
+ String? patientName;
+ String? patientNameN;
+ String? nationalityID;
+ String? doctorName;
+ String? doctorNameN;
+ int? clinicID;
+ String? clinicDescription;
+ String? clinicDescriptionN;
+ String? appointmentDate;
+ int? appointmentNo;
+ String? insuranceID;
+ int? companyID;
+ String? companyName;
+ String? companyNameN;
+ String? companyAddress;
+ String? companyAddressN;
+ String? companyGroupAddress;
+ String? groupName;
+ String? groupNameN;
+ String? patientAddress;
+ String? vATNo;
+ String? paymentDate;
+ String? projectName;
+ num? totalDiscount;
+ num? totalPatientShareWithQuantity;
+ String? legalName;
+ String? legalNameN;
+ num? advanceAdjustment;
+ String? patientCityName;
+ String? patientCityNameN;
+ String? doctorImageURL;
+ List? listConsultation;
+
+ GetInvoiceDetailsResponseModel(
+ {this.projectID,
+ this.doctorID,
+ this.grandTotal,
+ this.quantity,
+ this.total,
+ this.discount,
+ this.subTotal,
+ this.invoiceNo,
+ this.createdOn,
+ this.procedureID,
+ this.procedureName,
+ this.procedureNameN,
+ this.procedurePrice,
+ this.patientShare,
+ this.companyShare,
+ this.totalPatientShare,
+ this.totalCompanyShare,
+ this.totalShare,
+ this.discountAmount,
+ this.vATPercentage,
+ this.patientVATAmount,
+ this.companyVATAmount,
+ this.totalVATAmount,
+ this.price,
+ this.patientID,
+ this.patientIdentificationNo,
+ this.patientName,
+ this.patientNameN,
+ this.nationalityID,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicID,
+ this.clinicDescription,
+ this.clinicDescriptionN,
+ this.appointmentDate,
+ this.appointmentNo,
+ this.insuranceID,
+ this.companyID,
+ this.companyName,
+ this.companyNameN,
+ this.companyAddress,
+ this.companyAddressN,
+ this.companyGroupAddress,
+ this.groupName,
+ this.groupNameN,
+ this.patientAddress,
+ this.vATNo,
+ this.paymentDate,
+ this.projectName,
+ this.totalDiscount,
+ this.totalPatientShareWithQuantity,
+ this.legalName,
+ this.legalNameN,
+ this.advanceAdjustment,
+ this.patientCityName,
+ this.patientCityNameN,
+ this.doctorImageURL,
+ this.listConsultation});
+
+ GetInvoiceDetailsResponseModel.fromJson(Map json) {
+ projectID = json['ProjectID'];
+ doctorID = json['DoctorID'];
+ grandTotal = json['GrandTotal'];
+ quantity = json['Quantity'];
+ total = json['Total'];
+ discount = json['Discount'];
+ subTotal = json['SubTotal'];
+ invoiceNo = json['InvoiceNo'];
+ createdOn = json['CreatedOn'];
+ procedureID = json['ProcedureID'];
+ procedureName = json['ProcedureName'];
+ procedureNameN = json['ProcedureNameN'];
+ procedurePrice = json['ProcedurePrice'];
+ patientShare = json['PatientShare'];
+ companyShare = json['CompanyShare'];
+ totalPatientShare = json['TotalPatientShare'];
+ totalCompanyShare = json['TotalCompanyShare'];
+ totalShare = json['TotalShare'];
+ discountAmount = json['DiscountAmount'];
+ vATPercentage = json['VATPercentage'];
+ patientVATAmount = json['PatientVATAmount'];
+ companyVATAmount = json['CompanyVATAmount'];
+ totalVATAmount = json['TotalVATAmount'];
+ price = json['Price'];
+ patientID = json['PatientID'];
+ patientIdentificationNo = json['PatientIdentificationNo'];
+ patientName = json['PatientName'];
+ patientNameN = json['PatientNameN'];
+ nationalityID = json['NationalityID'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicID = json['ClinicID'];
+ clinicDescription = json['ClinicDescription'];
+ clinicDescriptionN = json['ClinicDescriptionN'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentNo = json['AppointmentNo'];
+ insuranceID = json['InsuranceID'];
+ companyID = json['CompanyID'];
+ companyName = json['CompanyName'];
+ companyNameN = json['CompanyNameN'];
+ companyAddress = json['CompanyAddress'];
+ companyAddressN = json['CompanyAddressN'];
+ companyGroupAddress = json['CompanyGroupAddress'];
+ groupName = json['GroupName'];
+ groupNameN = json['GroupNameN'];
+ patientAddress = json['PatientAddress'];
+ vATNo = json['VATNo'];
+ paymentDate = json['PaymentDate'];
+ projectName = json['ProjectName'];
+ totalDiscount = json['TotalDiscount'];
+ totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
+ legalName = json['LegalName'];
+ legalNameN = json['LegalNameN'];
+ advanceAdjustment = json['AdvanceAdjustment'];
+ patientCityName = json['PatientCityName'];
+ patientCityNameN = json['PatientCityNameN'];
+ doctorImageURL = json['DoctorImageURL'];
+ if (json['listConsultation'] != null) {
+ listConsultation = [];
+ json['listConsultation'].forEach((v) {
+ listConsultation!.add(new ListConsultation.fromJson(v));
+ });
+ }
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['ProjectID'] = this.projectID;
+ data['DoctorID'] = this.doctorID;
+ data['GrandTotal'] = this.grandTotal;
+ data['Quantity'] = this.quantity;
+ data['Total'] = this.total;
+ data['Discount'] = this.discount;
+ data['SubTotal'] = this.subTotal;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['CreatedOn'] = this.createdOn;
+ data['ProcedureID'] = this.procedureID;
+ data['ProcedureName'] = this.procedureName;
+ data['ProcedureNameN'] = this.procedureNameN;
+ data['ProcedurePrice'] = this.procedurePrice;
+ data['PatientShare'] = this.patientShare;
+ data['CompanyShare'] = this.companyShare;
+ data['TotalPatientShare'] = this.totalPatientShare;
+ data['TotalCompanyShare'] = this.totalCompanyShare;
+ data['TotalShare'] = this.totalShare;
+ data['DiscountAmount'] = this.discountAmount;
+ data['VATPercentage'] = this.vATPercentage;
+ data['PatientVATAmount'] = this.patientVATAmount;
+ data['CompanyVATAmount'] = this.companyVATAmount;
+ data['TotalVATAmount'] = this.totalVATAmount;
+ data['Price'] = this.price;
+ data['PatientID'] = this.patientID;
+ data['PatientIdentificationNo'] = this.patientIdentificationNo;
+ data['PatientName'] = this.patientName;
+ data['PatientNameN'] = this.patientNameN;
+ data['NationalityID'] = this.nationalityID;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicID'] = this.clinicID;
+ data['ClinicDescription'] = this.clinicDescription;
+ data['ClinicDescriptionN'] = this.clinicDescriptionN;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['InsuranceID'] = this.insuranceID;
+ data['CompanyID'] = this.companyID;
+ data['CompanyName'] = this.companyName;
+ data['CompanyNameN'] = this.companyNameN;
+ data['CompanyAddress'] = this.companyAddress;
+ data['CompanyAddressN'] = this.companyAddressN;
+ data['CompanyGroupAddress'] = this.companyGroupAddress;
+ data['GroupName'] = this.groupName;
+ data['GroupNameN'] = this.groupNameN;
+ data['PatientAddress'] = this.patientAddress;
+ data['VATNo'] = this.vATNo;
+ data['PaymentDate'] = this.paymentDate;
+ data['ProjectName'] = this.projectName;
+ data['TotalDiscount'] = this.totalDiscount;
+ data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
+ data['LegalName'] = this.legalName;
+ data['LegalNameN'] = this.legalNameN;
+ data['AdvanceAdjustment'] = this.advanceAdjustment;
+ data['PatientCityName'] = this.patientCityName;
+ data['PatientCityNameN'] = this.patientCityNameN;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ if (this.listConsultation != null) {
+ data['listConsultation'] =
+ this.listConsultation!.map((v) => v.toJson()).toList();
+ }
+ return data;
+ }
+}
+
+class ListConsultation {
+ int? projectID;
+ int? doctorID;
+ num? grandTotal;
+ int? quantity;
+ num? total;
+ num? discount;
+ num? subTotal;
+ int? invoiceNo;
+ String? createdOn;
+ String? procedureID;
+ String? procedureName;
+ String? procedureNameN;
+ num? procedurePrice;
+ num? patientShare;
+ num? companyShare;
+ num? totalPatientShare;
+ num? totalCompanyShare;
+ num? totalShare;
+ num? discountAmount;
+ num? vATPercentage;
+ num? patientVATAmount;
+ num? companyVATAmount;
+ num? totalVATAmount;
+ num? price;
+ int? patientID;
+ int? patientIdentificationNo;
+ String? patientName;
+ String? patientNameN;
+ String? nationalityID;
+ String? doctorName;
+ String? doctorNameN;
+ int? clinicID;
+ String? clinicDescription;
+ String? clinicDescriptionN;
+ String? appointmentDate;
+ dynamic appointmentNo;
+ dynamic insuranceID;
+ dynamic companyID;
+ String? companyName;
+ String? companyNameN;
+ String? companyAddress;
+ String? companyAddressN;
+ String? companyGroupAddress;
+ String? groupName;
+ String? groupNameN;
+ String? patientAddress;
+ String? vATNo;
+ String? paymentDate;
+ String? projectName;
+ num? totalDiscount;
+ num? totalPatientShareWithQuantity;
+ String? legalName;
+ String? legalNameN;
+ num? advanceAdjustment;
+ String? patientCityName;
+ String? patientCityNameN;
+
+ ListConsultation(
+ {this.projectID,
+ this.doctorID,
+ this.grandTotal,
+ this.quantity,
+ this.total,
+ this.discount,
+ this.subTotal,
+ this.invoiceNo,
+ this.createdOn,
+ this.procedureID,
+ this.procedureName,
+ this.procedureNameN,
+ this.procedurePrice,
+ this.patientShare,
+ this.companyShare,
+ this.totalPatientShare,
+ this.totalCompanyShare,
+ this.totalShare,
+ this.discountAmount,
+ this.vATPercentage,
+ this.patientVATAmount,
+ this.companyVATAmount,
+ this.totalVATAmount,
+ this.price,
+ this.patientID,
+ this.patientIdentificationNo,
+ this.patientName,
+ this.patientNameN,
+ this.nationalityID,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicID,
+ this.clinicDescription,
+ this.clinicDescriptionN,
+ this.appointmentDate,
+ this.appointmentNo,
+ this.insuranceID,
+ this.companyID,
+ this.companyName,
+ this.companyNameN,
+ this.companyAddress,
+ this.companyAddressN,
+ this.companyGroupAddress,
+ this.groupName,
+ this.groupNameN,
+ this.patientAddress,
+ this.vATNo,
+ this.paymentDate,
+ this.projectName,
+ this.totalDiscount,
+ this.totalPatientShareWithQuantity,
+ this.legalName,
+ this.legalNameN,
+ this.advanceAdjustment,
+ this.patientCityName,
+ this.patientCityNameN});
+
+ ListConsultation.fromJson(Map json) {
+ projectID = json['ProjectID'];
+ doctorID = json['DoctorID'];
+ grandTotal = json['GrandTotal'];
+ quantity = json['Quantity'];
+ total = json['Total'];
+ discount = json['Discount'];
+ subTotal = json['SubTotal'];
+ invoiceNo = json['InvoiceNo'];
+ createdOn = json['CreatedOn'];
+ procedureID = json['ProcedureID'];
+ procedureName = json['ProcedureName'];
+ procedureNameN = json['ProcedureNameN'];
+ procedurePrice = json['ProcedurePrice'];
+ patientShare = json['PatientShare'];
+ companyShare = json['CompanyShare'];
+ totalPatientShare = json['TotalPatientShare'];
+ totalCompanyShare = json['TotalCompanyShare'];
+ totalShare = json['TotalShare'];
+ discountAmount = json['DiscountAmount'];
+ vATPercentage = json['VATPercentage'];
+ patientVATAmount = json['PatientVATAmount'];
+ companyVATAmount = json['CompanyVATAmount'];
+ totalVATAmount = json['TotalVATAmount'];
+ price = json['Price'];
+ patientID = json['PatientID'];
+ patientIdentificationNo = json['PatientIdentificationNo'];
+ patientName = json['PatientName'];
+ patientNameN = json['PatientNameN'];
+ nationalityID = json['NationalityID'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicID = json['ClinicID'];
+ clinicDescription = json['ClinicDescription'];
+ clinicDescriptionN = json['ClinicDescriptionN'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentNo = json['AppointmentNo'];
+ insuranceID = json['InsuranceID'];
+ companyID = json['CompanyID'];
+ companyName = json['CompanyName'];
+ companyNameN = json['CompanyNameN'];
+ companyAddress = json['CompanyAddress'];
+ companyAddressN = json['CompanyAddressN'];
+ companyGroupAddress = json['CompanyGroupAddress'];
+ groupName = json['GroupName'];
+ groupNameN = json['GroupNameN'];
+ patientAddress = json['PatientAddress'];
+ vATNo = json['VATNo'];
+ paymentDate = json['PaymentDate'];
+ projectName = json['ProjectName'];
+ totalDiscount = json['TotalDiscount'];
+ totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
+ legalName = json['LegalName'];
+ legalNameN = json['LegalNameN'];
+ advanceAdjustment = json['AdvanceAdjustment'];
+ patientCityName = json['PatientCityName'];
+ patientCityNameN = json['PatientCityNameN'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['ProjectID'] = this.projectID;
+ data['DoctorID'] = this.doctorID;
+ data['GrandTotal'] = this.grandTotal;
+ data['Quantity'] = this.quantity;
+ data['Total'] = this.total;
+ data['Discount'] = this.discount;
+ data['SubTotal'] = this.subTotal;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['CreatedOn'] = this.createdOn;
+ data['ProcedureID'] = this.procedureID;
+ data['ProcedureName'] = this.procedureName;
+ data['ProcedureNameN'] = this.procedureNameN;
+ data['ProcedurePrice'] = this.procedurePrice;
+ data['PatientShare'] = this.patientShare;
+ data['CompanyShare'] = this.companyShare;
+ data['TotalPatientShare'] = this.totalPatientShare;
+ data['TotalCompanyShare'] = this.totalCompanyShare;
+ data['TotalShare'] = this.totalShare;
+ data['DiscountAmount'] = this.discountAmount;
+ data['VATPercentage'] = this.vATPercentage;
+ data['PatientVATAmount'] = this.patientVATAmount;
+ data['CompanyVATAmount'] = this.companyVATAmount;
+ data['TotalVATAmount'] = this.totalVATAmount;
+ data['Price'] = this.price;
+ data['PatientID'] = this.patientID;
+ data['PatientIdentificationNo'] = this.patientIdentificationNo;
+ data['PatientName'] = this.patientName;
+ data['PatientNameN'] = this.patientNameN;
+ data['NationalityID'] = this.nationalityID;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicID'] = this.clinicID;
+ data['ClinicDescription'] = this.clinicDescription;
+ data['ClinicDescriptionN'] = this.clinicDescriptionN;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['InsuranceID'] = this.insuranceID;
+ data['CompanyID'] = this.companyID;
+ data['CompanyName'] = this.companyName;
+ data['CompanyNameN'] = this.companyNameN;
+ data['CompanyAddress'] = this.companyAddress;
+ data['CompanyAddressN'] = this.companyAddressN;
+ data['CompanyGroupAddress'] = this.companyGroupAddress;
+ data['GroupName'] = this.groupName;
+ data['GroupNameN'] = this.groupNameN;
+ data['PatientAddress'] = this.patientAddress;
+ data['VATNo'] = this.vATNo;
+ data['PaymentDate'] = this.paymentDate;
+ data['ProjectName'] = this.projectName;
+ data['TotalDiscount'] = this.totalDiscount;
+ data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
+ data['LegalName'] = this.legalName;
+ data['LegalNameN'] = this.legalNameN;
+ data['AdvanceAdjustment'] = this.advanceAdjustment;
+ data['PatientCityName'] = this.patientCityName;
+ data['PatientCityNameN'] = this.patientCityNameN;
+ return data;
+ }
+}
diff --git a/lib/features/my_invoices/models/get_invoices_list_response_model.dart b/lib/features/my_invoices/models/get_invoices_list_response_model.dart
new file mode 100644
index 0000000..e8056d9
--- /dev/null
+++ b/lib/features/my_invoices/models/get_invoices_list_response_model.dart
@@ -0,0 +1,88 @@
+class GetInvoicesListResponseModel {
+ String? setupId;
+ int? projectID;
+ int? patientID;
+ int? appointmentNo;
+ String? appointmentDate;
+ String? appointmentDateN;
+ int? clinicID;
+ int? doctorID;
+ int? invoiceNo;
+ int? status;
+ String? arrivedOn;
+ String? doctorName;
+ String? doctorNameN;
+ String? clinicName;
+ double? decimalDoctorRate;
+ String? doctorImageURL;
+ int? doctorRate;
+ int? patientNumber;
+ String? projectName;
+
+ GetInvoicesListResponseModel(
+ {this.setupId,
+ this.projectID,
+ this.patientID,
+ this.appointmentNo,
+ this.appointmentDate,
+ this.appointmentDateN,
+ this.clinicID,
+ this.doctorID,
+ this.invoiceNo,
+ this.status,
+ this.arrivedOn,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicName,
+ this.decimalDoctorRate,
+ this.doctorImageURL,
+ this.doctorRate,
+ this.patientNumber,
+ this.projectName});
+
+ GetInvoicesListResponseModel.fromJson(Map json) {
+ setupId = json['SetupId'];
+ projectID = json['ProjectID'];
+ patientID = json['PatientID'];
+ appointmentNo = json['AppointmentNo'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentDateN = json['AppointmentDateN'];
+ clinicID = json['ClinicID'];
+ doctorID = json['DoctorID'];
+ invoiceNo = json['InvoiceNo'];
+ status = json['Status'];
+ arrivedOn = json['ArrivedOn'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicName = json['ClinicName'];
+ decimalDoctorRate = json['DecimalDoctorRate'];
+ doctorImageURL = json['DoctorImageURL'];
+ doctorRate = json['DoctorRate'];
+ patientNumber = json['PatientNumber'];
+ projectName = json['ProjectName'];
+ }
+
+ Map toJson() {
+ final Map data = {};
+ data['SetupId'] = this.setupId;
+ data['ProjectID'] = this.projectID;
+ data['PatientID'] = this.patientID;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentDateN'] = this.appointmentDateN;
+ data['ClinicID'] = this.clinicID;
+ data['DoctorID'] = this.doctorID;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['Status'] = this.status;
+ data['ArrivedOn'] = this.arrivedOn;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicName'] = this.clinicName;
+ data['DecimalDoctorRate'] = this.decimalDoctorRate;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ data['DoctorRate'] = this.doctorRate;
+ data['PatientNumber'] = this.patientNumber;
+ data['ProjectName'] = this.projectName;
+ return data;
+ }
+}
diff --git a/lib/features/my_invoices/my_invoices_repo.dart b/lib/features/my_invoices/my_invoices_repo.dart
new file mode 100644
index 0000000..68eee6e
--- /dev/null
+++ b/lib/features/my_invoices/my_invoices_repo.dart
@@ -0,0 +1,141 @@
+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/my_invoices/models/get_invoice_details_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+abstract class MyInvoicesRepo {
+ Future>>> getAllInvoicesList();
+
+ Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
+
+ Future>> sendInvoiceEmail({required num appointmentNo, required int projectID});
+}
+
+class MyInvoicesRepoImp implements MyInvoicesRepo {
+ final ApiClient apiClient;
+ final LoggerService loggerService;
+
+ MyInvoicesRepoImp({required this.loggerService, required this.apiClient});
+
+ @override
+ Future>>> getAllInvoicesList() async {
+ Map mapDevice = {};
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['List_DentalAppointments'];
+
+ final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map)).toList().cast();
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: invoicesList,
+ );
+ } 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()));
+ }
+ }
+
+ @override
+ Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async {
+ Map mapDevice = {
+ "AppointmentNo": appointmentNo,
+ "InvoiceNo": invoiceNo,
+ "IsRegistered": true,
+ "ProjectID": projectID,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_DENTAL_APPOINTMENT_INVOICE,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['List_eInvoiceForDental'];
+ final invoicesList = GetInvoiceDetailsResponseModel.fromJson(list[0]);
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: invoicesList,
+ );
+ } 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()));
+ }
+ }
+
+ @override
+ Future> sendInvoiceEmail({required num appointmentNo, required int projectID}) async {
+ Map mapDevice = {
+ "AppointmentNo": appointmentNo,
+ "IsRegistered": true,
+ "ProjectID": projectID,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: response,
+ );
+ } 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_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart
new file mode 100644
index 0000000..a02d741
--- /dev/null
+++ b/lib/features/my_invoices/my_invoices_view_model.dart
@@ -0,0 +1,99 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
+import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
+
+class MyInvoicesViewModel extends ChangeNotifier {
+ bool isInvoicesListLoading = false;
+ bool isInvoiceDetailsLoading = false;
+
+ MyInvoicesRepo myInvoicesRepo;
+ ErrorHandlerService errorHandlerService;
+ NavigationService navServices;
+
+ List allInvoicesList = [];
+ late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel;
+
+ MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
+
+ setInvoicesListLoading() {
+ isInvoicesListLoading = true;
+ allInvoicesList.clear();
+ notifyListeners();
+ }
+
+ setInvoiceDetailLoading() {
+ isInvoiceDetailsLoading = true;
+ notifyListeners();
+ }
+
+ Future getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.getAllInvoicesList();
+
+ result.fold(
+ (failure) async {
+ isInvoicesListLoading = false;
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ allInvoicesList = apiResponse.data!;
+ isInvoicesListLoading = false;
+
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
+
+ result.fold(
+ (failure) async {
+ isInvoiceDetailsLoading = false;
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ invoiceDetailsResponseModel = apiResponse.data!;
+ isInvoiceDetailsLoading = false;
+
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID);
+
+ result.fold(
+ (failure) async {
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+}
diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart
index e7a4f07..c6e7150 100644
--- a/lib/features/prescriptions/prescriptions_repo.dart
+++ b/lib/features/prescriptions/prescriptions_repo.dart
@@ -182,6 +182,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo {
"To": Utils.appState.getAuthenticatedUser()!.emailAddress,
"SetupID": prescriptionsResponseModel.setupID,
"IsDownload": true,
+ "isDentalAllowedBackend": false,
};
try {
diff --git a/lib/main.dart b/lib/main.dart
index 9600d5e..a9d16dc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -25,6 +25,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode
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_view_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_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';
@@ -169,6 +170,9 @@ void main() async {
),
ChangeNotifierProvider(
create: (_) => getIt.get(),
+ ),
+ ChangeNotifierProvider(
+ create: (_) => getIt.get(),
)
], child: MyApp()),
),
diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart
index dae8d25..bd81584 100644
--- a/lib/presentation/appointments/appointment_details_page.dart
+++ b/lib/presentation/appointments/appointment_details_page.dart
@@ -246,7 +246,7 @@ class _AppointmentDetailsPageState extends State {
],
),
SizedBox(height: 8.h),
- Wrap(
+ bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap(
direction: Axis.horizontal,
spacing: 8.w,
runSpacing: 8.h,
@@ -261,7 +261,7 @@ class _AppointmentDetailsPageState extends State {
"Nearest Gate: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
],
- ),
+ ) : SizedBox.shrink(),
],
),
),
diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart
index eb72bc6..b4c3630 100644
--- a/lib/presentation/appointments/my_appointments_page.dart
+++ b/lib/presentation/appointments/my_appointments_page.dart
@@ -4,9 +4,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
-import 'package:hmg_patient_app_new/core/app_state.dart';
-import 'package:hmg_patient_app_new/core/dependencies.dart';
-import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart
index 3740a4a..2c5d1b0 100644
--- a/lib/presentation/appointments/my_doctors_page.dart
+++ b/lib/presentation/appointments/my_doctors_page.dart
@@ -1,8 +1,6 @@
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/app_state.dart';
-import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@@ -43,8 +41,8 @@ class _MyDoctorsPageState extends State {
bookAppointmentsViewModel = Provider.of(context, listen: false);
return CollapsingListView(
- title: LocaleKeys.myDoctor.tr(context: context),
- child: Consumer(builder: (context, myAppointmentsVM, child) {
+ title: LocaleKeys.myDoctor.tr(context: context),
+ child: Consumer(builder: (context, myAppointmentsVM, child) {
// build grouped lists from the flat list
final clinicMap = >{};
final hospitalMap = >{};
@@ -171,129 +169,121 @@ class _MyDoctorsPageState extends State {
final displayName = isSortByClinic ? (group.first.clinicName ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
final isExpanded = expandedIndex == index;
- return Container(
- key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
- margin: EdgeInsets.only(bottom: 12.h),
- padding: EdgeInsets.all(16.h),
+ return AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- GestureDetector(
- onTap: () {
- setState(() {
- expandedIndex = isExpanded ? null : index;
- });
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final key = _groupKeys[index];
- if (key != null && key.currentContext != null && expandedIndex == index) {
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
+ if (key.currentContext != null && expandedIndex == index) {
+ Future.delayed(const Duration(milliseconds: 450), () {
+ if (key.currentContext != null) {
Scrollable.ensureVisible(
key.currentContext!,
- duration: Duration(milliseconds: 350),
+ duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
- alignment: 0.1,
+ alignment: 0.0,
);
}
});
- },
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- CustomButton(
- text: "${group.length} ${'doctors'.needTranslation}",
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 30.h,
- ),
- Icon(isExpanded ? Icons.expand_less : Icons.chevron_right, color: AppColors.greyColor),
- ],
- ),
- SizedBox(height: 8.h),
- Text(
- displayName,
- style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
- overflow: TextOverflow.ellipsis,
- ),
- ],
+ }
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AppCustomChipWidget(labelText: "${group.length} ${'doctors'.needTranslation}"),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Text(
+ displayName,
+ style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
),
- ),
- AnimatedSwitcher(
- duration: Duration(milliseconds: 400),
- child: isExpanded
- ? Container(
- key: ValueKey(index),
- padding: EdgeInsets.only(top: 12.h),
- child: Column(
- children: group.map((doctor) {
- return Container(
- margin: EdgeInsets.only(bottom: 12.h),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 12.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
- child: Column(
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...group.map((doctor) {
+ return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
- crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
children: [
Image.network(
(doctor?.doctorImageURL ?? doctor?.doctorImage ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png"),
- width: 24.h,
+ width: 24.w,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
- SizedBox(height: 6.h),
- ],
- ),
+ child: (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
),
],
),
SizedBox(height: 8.h),
- Row(
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 4.h,
+ runSpacing: 4.h,
children: [
- CustomButton(
- text: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
+ AppCustomChipWidget(
+ labelText: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
),
],
),
- SizedBox(height: 8.h),
+ SizedBox(height: 12.h),
Row(
children: [
Expanded(
- flex: 6,
+ flex: 2,
child: CustomButton(
+ icon: AppAssets.view_report_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
text: "View Profile".needTranslation.tr(context: context),
onPressed: () async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
@@ -320,75 +310,31 @@ class _MyDoctorsPageState extends State {
);
});
},
- backgroundColor: AppColors.bgRedLightColor,
- borderColor: AppColors.primaryRedColor,
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
- SizedBox(width: 8.h),
- Expanded(
- flex: 1,
- child: Container(
- height: 40.h,
- width: 40.w,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.textColor,
- borderRadius: 12,
- ),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Transform.flip(
- flipX: getIt().isArabic(),
- child: Utils.buildSvgWithAssets(
- icon: AppAssets.forward_arrow_icon_small,
- iconColor: AppColors.whiteColor,
- fit: BoxFit.contain,
- ),
- ),
- ),
- ).onPress(() async {
- bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
- clinicID: doctor?.clinicID ?? 0,
- projectID: doctor?.projectID ?? 0,
- doctorID: doctor?.doctorID ?? 0,
- ));
- LoaderBottomSheet.showLoader();
- await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
- LoaderBottomSheet.hideLoader();
- Navigator.of(context).push(
- CustomPageRoute(
- page: DoctorProfilePage(),
- ),
- );
- }, onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- }),
- ),
],
),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
],
- ),
- ),
- );
- }).toList(),
- ),
- )
- : SizedBox.shrink(),
- ),
- ],
+ );
+ }).toList(),
+ ],
+ ),
+ )
+ : const SizedBox.shrink(),
+ ),
+ ],
+ ),
),
);
},
diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart
index 6849253..64dc803 100644
--- a/lib/presentation/appointments/widgets/appointment_card.dart
+++ b/lib/presentation/appointments/widgets/appointment_card.dart
@@ -179,7 +179,13 @@ class AppointmentCard extends StatelessWidget {
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
).toShimmer2(isShow: isLoading),
- AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!).toShimmer2(isShow: isLoading),
+ AppCustomChipWidget(
+ labelText: isLoading
+ ? 'Olaya'
+ : patientAppointmentHistoryResponseModel.projectName!.length > 15
+ ? '${patientAppointmentHistoryResponseModel.projectName!.substring(0, 12)}...'
+ : patientAppointmentHistoryResponseModel.projectName!)
+ .toShimmer2(isShow: isLoading),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.appointment_calendar_icon,
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
index ad48a6d..eed45df 100644
--- 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
@@ -1,58 +1,151 @@
import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
+import 'package:hmg_patient_app_new/core/utils/debouncer.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.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/facility_selection.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/hospital_bottom_sheet/hospital_list_items.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';
+import '../../../../features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
+import '../../../emergency_services/call_ambulance/widgets/type_selection_widget.dart' show TypeSelectionWidget;
+
+// class HospitalBottomSheetBody extends StatelessWidget {
+// late BookAppointmentsViewModel 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: LocaleKeys.searchHospital.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) {
+// var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
+// ? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index]
+// : appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index];
+// return HospitalListItem(
+// hospitalData: hospital,
+// isLocationEnabled: appointmentsViewModel.isLocationEnabled(),
+// ).onPress(() {
+// regionalViewModel.setHospitalModel(hospital);
+// if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
+// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
+// regionalViewModel.handleLastStepForRegion();
+// } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
+// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+// regionalViewModel.handleLastStepForClinic();
+// } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
+// regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+// regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
+// // regionalViewModel.handleLastStepForClinic();
+// }
+// });
+// },
+// 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),
+// )
+// ],
+// );
+// }
+// }
+
class HospitalBottomSheetBody extends StatelessWidget {
- late BookAppointmentsViewModel appointmentsViewModel;
- late AppointmentViaRegionViewmodel regionalViewModel;
- final TextEditingController searchText = TextEditingController();
- HospitalBottomSheetBody({super.key});
+ final TextEditingController searchText ;
+ final Debouncer debouncer = Debouncer(milliseconds: 500);
+
+ final int hmcCount;
+ final int hmgCount;
+ final List? displayList;
+ final FacilitySelection selectedFacility;
+ final Function(FacilitySelection) onFacilityClicked;
+ final Function(PatientDoctorAppointmentList) onHospitalClicked;
+ final Function(String) onHospitalSearch;
+
+ HospitalBottomSheetBody({super.key, required this.hmcCount, required this.hmgCount, this.displayList, required this.selectedFacility, required this.onFacilityClicked, required this.onHospitalClicked, required this.onHospitalSearch, required this.searchText});
@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: LocaleKeys.searchHospital.tr(),
controller: searchText,
onChange: (value) {
- appointmentsViewModel.filterHospitalListByString(
- value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name);
+ debouncer.run((){
+ onHospitalSearch(value??"");
+ });
},
isEnable: true,
prefix: null,
+
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
@@ -64,46 +157,34 @@ class HospitalBottomSheetBody extends StatelessWidget {
),
),
SizedBox(height: 24.h),
- // TypeSelectionWidget(
- // hmcCount: "0",
- // hmgCount: "0",
- // ),
- // SizedBox(height: 21.h),
+ TypeSelectionWidget(
+ selectedFacility:selectedFacility ,
+ hmcCount: hmcCount.toString(),
+ hmgCount: hmgCount.toString(),
+ onitemClicked: (selectedValue){
+ onFacilityClicked(selectedValue);
+ },
+ ),
+ SizedBox(height: 21.h),
SizedBox(
- height: MediaQuery.sizeOf(context).height * .4,
- child: ListView.separated(
- itemBuilder: (_, index) {
- var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name
- ? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index]
- : appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index];
+ height: MediaQuery.sizeOf(context).height * .4,
+ child: ListView.separated(
+ itemBuilder: (_, index)
+ {
+ var hospital = displayList?[index];
return HospitalListItem(
hospitalData: hospital,
- isLocationEnabled: appointmentsViewModel.isLocationEnabled(),
+ isLocationEnabled: true,
).onPress(() {
- regionalViewModel.setHospitalModel(hospital);
- if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
- regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
- regionalViewModel.handleLastStepForRegion();
- } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
- regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
- regionalViewModel.handleLastStepForClinic();
- } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
- regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
- regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
- // regionalViewModel.handleLastStepForClinic();
- }
- });
- },
+ onHospitalClicked(hospital!);
+ });},
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),
- )
+ height: 16.h,
+ ),
+ itemCount: displayList?.length ?? 0,
+ ))
],
);
}
}
+
diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart
index 2def72c..6588c2e 100644
--- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart
+++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart
@@ -79,7 +79,8 @@ class _RegionBottomSheetBodyState extends State {
hmgCount: "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}",
).onPress(() {
regionalViewModel.setSelectedRegionId(key);
- regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ regionalViewModel.setDisplayListAndRegionHospitalList(myAppointmentsVM.hospitalList?.registeredDoctorMap![key]);
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION);
});
},
),
diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart
index 93b26c7..f0fd8cb 100644
--- a/lib/presentation/authentication/saved_login_screen.dart
+++ b/lib/presentation/authentication/saved_login_screen.dart
@@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class SavedLogin extends StatefulWidget {
@@ -266,9 +267,15 @@ class _SavedLogin extends State {
child: CustomButton(
text: LocaleKeys.guest.tr(),
onPressed: () {
- Navigator.of(context).pushReplacement(
- MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()),
- );
+ Navigator.pushAndRemoveUntil(
+ context,
+ CustomPageRoute(
+ page: LandingNavigation(),
+ ),
+ (r) => false);
+ // Navigator.of(context).pushAndRemoveUntil(
+ // MaterialPageRoute(builder: (BuildContext context) => LandingNavigation())
+ // );
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart
index 5aa1b7c..76dc1be 100644
--- a/lib/presentation/book_appointment/book_appointment_page.dart
+++ b/lib/presentation/book_appointment/book_appointment_page.dart
@@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
+// import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class BookAppointmentPage extends StatefulWidget {
const BookAppointmentPage({super.key});
@@ -511,7 +512,34 @@ class _BookAppointmentPageState extends State {
);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
- return HospitalBottomSheetBody();
+ return HospitalBottomSheetBody(
+ searchText: data.searchController,
+ displayList: data.displayList,
+ onFacilityClicked: (value) {
+ data.setSelectedFacility(value);
+ data.getDisplayList();
+ },
+ onHospitalClicked: (hospital) {
+ regionalViewModel.setHospitalModel(hospital);
+ if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
+ regionalViewModel.handleLastStepForRegion();
+ } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+ regionalViewModel.handleLastStepForClinic();
+ } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
+ var appointmentsViewModel = Provider.of(context);
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+ regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
+ }
+ },
+ onHospitalSearch: (value) {
+ data.searchHospitals(value ?? "");
+ },
+ selectedFacility: data.selectedFacility,
+ hmcCount: data.hmcCount,
+ hmgCount: data.hmgCount,
+ );
}
if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) {
// Navigator.of(context).pop();
diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart
index 450e86b..48c0cd5 100644
--- a/lib/presentation/book_appointment/select_clinic_page.dart
+++ b/lib/presentation/book_appointment/select_clinic_page.dart
@@ -1019,12 +1019,12 @@ class _SelectClinicPageState extends State {
regionalViewModel.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false,
- child: Consumer(builder: (_, data, __) {
- return getRegionalSelectionWidget(data);
+ child: Consumer(builder: (context, data, __) {
+ return getRegionalSelectionWidget(data, context);
}), callBackFunc: () {});
}
- Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
+ Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) {
if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
return RegionBottomSheetBody();
}
@@ -1035,7 +1035,34 @@ class _SelectClinicPageState extends State {
);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
- return HospitalBottomSheetBody();
+ return HospitalBottomSheetBody(
+ searchText: data.searchController,
+ displayList: data.displayList,
+ onFacilityClicked: (value) {
+ data.setSelectedFacility(value);
+ data.getDisplayList();
+ },
+ onHospitalClicked: (hospital) {
+ regionalViewModel.setHospitalModel(hospital);
+ if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) {
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
+ regionalViewModel.handleLastStepForRegion();
+ } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) {
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+ regionalViewModel.handleLastStepForClinic();
+ } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
+ var appointmentsViewModel = Provider.of(context, listen: false);
+ regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION);
+ regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1);
+ }
+ },
+ onHospitalSearch: (value) {
+ data.searchHospitals(value ?? "");
+ },
+ selectedFacility: data.selectedFacility,
+ hmcCount: data.hmcCount,
+ hmgCount: data.hmgCount,
+ );
}
if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) {
//if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api
diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart
index 1380ad9..3b31d38 100644
--- a/lib/presentation/hmg_services/services_page.dart
+++ b/lib/presentation/hmg_services/services_page.dart
@@ -10,6 +10,7 @@ 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/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/medical_file/medical_file_view_model.dart';
@@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_mo
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
+import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart';
@@ -48,9 +50,21 @@ class ServicesPage extends StatelessWidget {
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
- true,
- route: AppRoutes.eReferralPage,
- ),
+ 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,
"Book\nAppointment".needTranslation,
@@ -58,7 +72,7 @@ class ServicesPage extends StatelessWidget {
AppAssets.appointment_calendar_icon,
bgColor: AppColors.bookAppointment,
true,
- route: AppRoutes.eReferralPage,
+ route: AppRoutes.bookAppointmentPage,
),
HmgServicesComponentModel(
5,
diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart
index 1496ded..95884d4 100644
--- a/lib/presentation/home/landing_page.dart
+++ b/lib/presentation/home/landing_page.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/int_extensions.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';
@@ -25,6 +27,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_
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/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';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
@@ -43,6 +46,9 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dar
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_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';
+import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
+import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@@ -67,13 +73,13 @@ class _LandingPageState extends State {
late AppState appState;
late MyAppointmentsViewModel myAppointmentsViewModel;
- late PrescriptionsViewModel prescriptionsViewModel;
final CacheService cacheService = GetIt.instance();
late AppointmentRatingViewModel appointmentRatingViewModel;
late InsuranceViewModel insuranceViewModel;
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late EmergencyServicesViewModel emergencyServicesViewModel;
+ late TodoSectionViewModel todoSectionViewModel;
final SwiperController _controller = SwiperController();
@@ -82,10 +88,6 @@ class _LandingPageState extends State {
authVM = context.read();
habibWalletVM = context.read();
appointmentRatingViewModel = context.read();
- // myAppointmentsViewModel = context.read();
- // prescriptionsViewModel = context.read();
- // insuranceViewModel = context.read();
- // immediateLiveCareViewModel = context.read();
authVM.savePushTokenToAppState();
if (mounted) {
@@ -97,13 +99,11 @@ class _LandingPageState extends State {
if (appState.isAuthenticated) {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
+ todoSectionViewModel.initializeTodoSectionViewModel();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
- myAppointmentsViewModel.getPatientMyDoctors();
- prescriptionsViewModel.initPrescriptionsViewModel();
- insuranceViewModel.initInsuranceProvider();
emergencyServicesViewModel.checkPatientERAdvanceBalance();
myAppointmentsViewModel.getPatientAppointmentQueueDetails();
if(!appState.isRatedVisible) {
@@ -111,17 +111,16 @@ class _LandingPageState extends State {
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);
-
- }));
-
- }
- });
+ }),
+ );
+ }
+ },
+ );
}
}
});
@@ -132,10 +131,10 @@ class _LandingPageState extends State {
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of(context, listen: false);
myAppointmentsViewModel = Provider.of(context, listen: false);
- prescriptionsViewModel = Provider.of(context, listen: false);
insuranceViewModel = Provider.of(context, listen: false);
immediateLiveCareViewModel = Provider.of(context, listen: false);
emergencyServicesViewModel = Provider.of(context, listen: false);
+ todoSectionViewModel = Provider.of(context, listen: false);
appState = getIt.get();
return PopScope(
canPop: false,
@@ -214,6 +213,50 @@ class _LandingPageState extends State {
),
],
).paddingSymmetrical(24.h, 0.h),
+ !appState.isAuthenticated
+ ? Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.r,
+ hasShadow: false,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(16.h),
+ child: Row(
+ children: [
+ Utils.buildSvgWithAssets(
+ width: 50.w,
+ height: 60.h,
+ icon: AppAssets.symptomCheckerIcon,
+ fit: BoxFit.contain,
+ ),
+ SizedBox(width: 12.w),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ "How are you feeling today?".needTranslation.toText14(isBold: true),
+ "Check your symptoms with this scale".needTranslation.toText12(fontWeight: FontWeight.w500),
+ SizedBox(height: 14.h),
+ CustomButton(
+ text: "Check your symptoms".needTranslation,
+ onPressed: () async {
+ context.navigateWithName(AppRoutes.userInfoSelection);
+ },
+ backgroundColor: Color(0xFF2B353E),
+ borderColor: Color(0xFF2B353E),
+ textColor: AppColors.whiteColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w600,
+ borderRadius: 12,
+ height: 40.h,
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ).paddingSymmetrical(24.w, 0.h)
+ : SizedBox.shrink(),
appState.isAuthenticated
? Column(
children: [
@@ -234,8 +277,8 @@ class _LandingPageState extends State {
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
SizedBox(height: 16.h),
- Consumer2(
- builder: (context, myAppointmentsVM, immediateLiveCareVM, child) {
+ Consumer3(
+ builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@@ -452,20 +495,41 @@ class _LandingPageState extends State {
SizedBox(height: 12.h),
],
)
- : 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,
- ),
+ : (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,
+ ),
);
},
)
diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart
index 6ec8c3a..43cc3b9 100644
--- a/lib/presentation/home/navigation_screen.dart
+++ b/lib/presentation/home/navigation_screen.dart
@@ -1,15 +1,18 @@
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/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
+import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class LandingNavigation extends StatefulWidget {
const LandingNavigation({super.key});
@@ -33,7 +36,9 @@ class _LandingNavigationState extends State {
const LandingPage(),
appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(),
SizedBox(),
- const ToDoPage(),
+ // const ToDoPage(),
+ // appState.isAuthenticated ? UserInfoSelectionScreen() : /* need add news page */ SizedBox(),
+ SizedBox(),
ServicesPage(),
],
),
@@ -46,6 +51,20 @@ class _LandingNavigationState extends State {
context.navigateWithName(AppRoutes.bookAppointmentPage);
return;
}
+ if (_currentIndex == 3) {
+ if (appState.isAuthenticated) {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: UserInfoSelectionScreen(),
+ ),
+ );
+ } else {
+ Utils.openWebView(
+ url: 'https://x.com/HMG',
+ );
+ }
+ return;
+ }
_pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
},
),
diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart
index 150b15c..415d66f 100644
--- a/lib/presentation/insurance/insurance_approval_details_page.dart
+++ b/lib/presentation/insurance/insurance_approval_details_page.dart
@@ -146,7 +146,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"${LocaleKeys.usageStatus.tr(context: context)}: ".toText14(isBold: true),
- insuranceApprovalResponseModel.apporvalDetails!.isInvoicedDesc!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
+ (insuranceApprovalResponseModel.apporvalDetails!.isInvoicedDesc ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
],
),
],
diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart
index 3d774d3..fde5811 100644
--- a/lib/presentation/insurance/widgets/patient_insurance_card.dart
+++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart
@@ -48,8 +48,9 @@ class PatientInsuranceCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true),
- "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor),
+ SizedBox(
+ width: MediaQuery.of(context).size.width * 0.45, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true)),
+ "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".needTranslation.toText12(isBold: true, color: AppColors.lightGrayColor),
],
),
AppCustomChipWidget(
diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart
index 27b11fa..f705993 100644
--- a/lib/presentation/medical_file/medical_file_page.dart
+++ b/lib/presentation/medical_file/medical_file_page.dart
@@ -15,6 +15,9 @@ 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/book_appointments/models/resp_models/doctors_list_response_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart';
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';
@@ -22,6 +25,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_res
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_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/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
@@ -46,8 +50,11 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_fi
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
+import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
+import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
+import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@@ -79,24 +86,50 @@ class _MedicalFilePageState extends State {
late MedicalFileViewModel medicalFileViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late LabViewModel labViewModel;
+ late MyInvoicesViewModel myInvoicesViewModel;
+ late HmgServicesViewModel hmgServicesViewModel;
+ late PrescriptionsViewModel prescriptionsViewModel;
int currentIndex = 0;
+ // Used to make the PageView height follow the card's intrinsic height
+ final GlobalKey _vitalSignMeasureKey = GlobalKey();
+ double? _vitalSignMeasuredHeight;
+
@override
void initState() {
appState = getIt.get();
scheduleMicrotask(() {
if (appState.isAuthenticated) {
- labViewModel.initLabProvider();
+ myAppointmentsViewModel.getPatientMyDoctors();
+ prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList();
medicalFileViewModel.onTabChanged(0);
+ // Load vital signs
+ hmgServicesViewModel.getPatientVitalSign();
}
});
super.initState();
}
+ void _scheduleVitalSignMeasure() {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final ctx = _vitalSignMeasureKey.currentContext;
+ if (ctx == null) return;
+ final box = ctx.findRenderObject();
+ if (box is RenderBox) {
+ final h = box.size.height;
+ if (h > 0 && h != _vitalSignMeasuredHeight) {
+ setState(() {
+ _vitalSignMeasuredHeight = h;
+ });
+ }
+ }
+ });
+ }
+
@override
Widget build(BuildContext context) {
labViewModel = Provider.of(context, listen: false);
@@ -104,9 +137,13 @@ class _MedicalFilePageState extends State {
myAppointmentsViewModel = Provider.of(context, listen: false);
medicalFileViewModel = Provider.of(context, listen: false);
bookAppointmentsViewModel = Provider.of(context, listen: false);
+ myInvoicesViewModel = Provider.of(context, listen: false);
+ hmgServicesViewModel = Provider.of(context, listen: false);
+ prescriptionsViewModel = Provider.of(context, listen: false);
NavigationService navigationService = getIt.get();
return CollapsingListView(
- title: "Medical File".needTranslation,
+ // title: "Medical File".needTranslation,
+ title: LocaleKeys.medicalFile.tr(context: context),
trailing: Row(
children: [
Wrap(
@@ -180,8 +217,11 @@ class _MedicalFilePageState extends State {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"
- .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1),
+ SizedBox(
+ width: MediaQuery.of(context).size.width * 0.6,
+ child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"
+ .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 2),
+ ),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
@@ -250,6 +290,111 @@ class _MedicalFilePageState extends State {
),
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
+
+ // Vital Signs Section
+ Consumer(builder: (context, hmgServicesVM, child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
+ Row(
+ children: [
+ LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
+ SizedBox(width: 2.h),
+ Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
+ ],
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 0.h).onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: VitalSignPage(),
+ ),
+ );
+ }),
+ SizedBox(height: 16.h),
+ // Make this section dynamic-height (no fixed 160.h)
+ LayoutBuilder(
+ builder: (context, constraints) {
+ if (hmgServicesVM.isVitalSignLoading) {
+ return _buildVitalSignShimmer();
+ }
+ if (hmgServicesVM.vitalSignList.isEmpty) {
+ return Container(
+ padding: EdgeInsets.all(16.w),
+ width: MediaQuery.of(context).size.width,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 12.r,
+ hasShadow: false,
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h),
+ SizedBox(height: 12.h),
+ "No vital signs recorded yet".needTranslation.toText12(isCenter: true),
+ ],
+ ),
+ );
+ }
+
+ // The cards define their own height; measure the first rendered page once
+ _scheduleVitalSignMeasure();
+ final double hostHeight = _vitalSignMeasuredHeight ?? (160.h);
+
+ return SizedBox(
+ height: hostHeight,
+ child: PageView(
+ controller: hmgServicesVM.vitalSignPageController,
+ onPageChanged: (index) {
+ hmgServicesVM.setVitalSignCurrentPage(index);
+ _scheduleVitalSignMeasure();
+ },
+ children: _buildVitalSignPages(
+ vitalSign: hmgServicesVM.vitalSignList.first,
+ onTap: () {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: VitalSignPage(),
+ ),
+ );
+ },
+ measureKey: _vitalSignMeasureKey,
+ currentPageIndex: hmgServicesVM.vitalSignCurrentPage,
+ ),
+ ),
+ );
+ },
+ ),
+ if (!hmgServicesVM.isVitalSignLoading && hmgServicesVM.vitalSignList.isNotEmpty) ...[
+ SizedBox(height: 12.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(
+ 2, // 2 pages (BMI+Height on page 1, Weight+BP on page 2)
+ (index) => Container(
+ margin: EdgeInsets.symmetric(horizontal: 3.w),
+ width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w,
+ height: 8.h,
+ decoration: BoxDecoration(
+ color: hmgServicesVM.vitalSignCurrentPage == index
+ ? AppColors.primaryRedColor
+ : AppColors.dividerColor,
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ],
+ ).paddingSymmetrical(24.w, 0.0);
+ }),
+ SizedBox(height: 16.h),
+
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: "Type any record".needTranslation,
@@ -268,7 +413,7 @@ class _MedicalFilePageState extends State {
// Using CustomExpandableList
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
- dividerColor: Color(0xFF2B353E1A),
+ dividerColor: Color(0xff2b353e1a),
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: [
ExpandableListItem(
@@ -485,47 +630,51 @@ class _MedicalFilePageState extends State {
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
- duration: Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- child: MedicalFileAppointmentCard(
- patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
- myAppointmentsViewModel: myAppointmentsViewModel,
- onRescheduleTap: () {
- openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
- },
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ child: MedicalFileAppointmentCard(
+ patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
+ myAppointmentsViewModel: myAppointmentsViewModel,
+ onRescheduleTap: () {
+ openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
+ },
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
- projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
- doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
- clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
- onSuccess: (value) async {
- if (value) {
- await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- title: LocaleKeys.askDoctor.tr(context: context),
- child: AskDoctorRequestTypeSelect(
- askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
- myAppointmentsViewModel: myAppointmentsViewModel,
- patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
- ),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- } else {
- print("Doctor is not available");
- }
- });
+ projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
+ doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
+ clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
+ onSuccess: (value) async {
+ if (value) {
+ await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ title: LocaleKeys.askDoctor.tr(context: context),
+ child: AskDoctorRequestTypeSelect(
+ askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
+ myAppointmentsViewModel: myAppointmentsViewModel,
+ patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
+ ),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ } else {
+ LoaderBottomSheet.hideLoader();
+ print("Doctor is not available");
+ }
+ },
+ onError: (_) {
+ LoaderBottomSheet.hideLoader();
+ },
+ );
},
),
),
),
- ),
- );
+ ));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
),
@@ -643,58 +792,57 @@ class _MedicalFilePageState extends State {
);
}),
),
+ ));
+ },
+ separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
+ ),
+ SizedBox(height: 16.h),
+ const Divider(color: AppColors.dividerColor),
+ SizedBox(height: 16.h),
+ Row(
+ children: [
+ Expanded(
+ child: CustomButton(
+ text: "All Prescriptions".needTranslation,
+ onPressed: () {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: PrescriptionsListPage(),
+ ),
+ );
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 12.f,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12.r,
+ height: 40.h,
+ icon: AppAssets.requests,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.w,
+ ),
),
- );
- },
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
- ),
- SizedBox(height: 16.h),
- const Divider(color: AppColors.dividerColor),
- SizedBox(height: 16.h),
- Row(
- children: [
- Expanded(
- child: CustomButton(
- text: "All Prescriptions".needTranslation,
- onPressed: () {
- Navigator.of(context).push(
- CustomPageRoute(
- page: PrescriptionsListPage(),
- ),
- );
- },
- backgroundColor: AppColors.secondaryLightRedColor,
- borderColor: AppColors.secondaryLightRedColor,
- textColor: AppColors.primaryRedColor,
- fontSize: 12.f,
- fontWeight: FontWeight.w500,
- borderRadius: 12.r,
- height: 40.h,
- icon: AppAssets.requests,
- iconColor: AppColors.primaryRedColor,
- iconSize: 16.w,
- ),
- ),
- SizedBox(width: 6.w),
- Expanded(
- child: CustomButton(
- text: "All Medications".needTranslation,
- onPressed: () {},
- backgroundColor: AppColors.secondaryLightRedColor,
- borderColor: AppColors.secondaryLightRedColor,
- textColor: AppColors.primaryRedColor,
- fontSize: 12.f,
- fontWeight: FontWeight.w500,
- borderRadius: 12.h,
- height: 40.h,
- icon: AppAssets.all_medications_icon,
- iconColor: AppColors.primaryRedColor,
- iconSize: 16.h,
+ SizedBox(width: 6.w),
+ Expanded(
+ child: CustomButton(
+ text: "All Medications".needTranslation,
+ onPressed: () {},
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 12.f,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12.h,
+ height: 40.h,
+ icon: AppAssets.all_medications_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
+ ),
),
- ),
- ],
- ),
- ],
+ ],
+ ),
+ ],
),
),
).paddingSymmetrical(0.w, 0.h)
@@ -826,8 +974,7 @@ class _MedicalFilePageState extends State {
});
}),
),
- ),
- );
+ ));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@@ -995,7 +1142,13 @@ class _MedicalFilePageState extends State {
svgIcon: AppAssets.invoices_list_icon,
isLargeText: true,
iconSize: 36.w,
- ),
+ ).onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: MyInvoicesList(),
+ ),
+ );
+ }),
MedicalFileCard(
label: "Ancillary Orders List".needTranslation,
textColor: AppColors.blackColor,
@@ -1003,7 +1156,13 @@ class _MedicalFilePageState extends State {
svgIcon: AppAssets.ancillary_orders_list_icon,
isLargeText: true,
iconSize: 36.w,
- ),
+ ).onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: ToDoPage(),
+ ),
+ );
+ }),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
@@ -1065,7 +1224,7 @@ class _MedicalFilePageState extends State {
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.medical_reports_icon,
isLargeText: true,
- iconSize: 36.h,
+ iconSize: 36.w,
).onPress(() {
medicalFileViewModel.setIsPatientMedicalReportsLoading(true);
medicalFileViewModel.getPatientMedicalReportList();
@@ -1185,4 +1344,265 @@ class _MedicalFilePageState extends State {
return Container();
}
}
+
+ // Build shimmer for vital signs
+ Widget _buildVitalSignShimmer() {
+ return Row(
+ children: [
+ Expanded(child: _buildSingleShimmerCard()),
+ SizedBox(width: 12.w),
+ Expanded(child: _buildSingleShimmerCard()),
+ ],
+ );
+ }
+
+ Widget _buildSingleShimmerCard() {
+ return Container(
+ decoration: BoxDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: BorderRadius.circular(16.r),
+ ),
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ // Icon shimmer at top
+ Container(
+ width: 44.w,
+ height: 44.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 16.h),
+ // Label shimmer
+ Container(
+ width: 70.w,
+ height: 12.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 8.h),
+ // Value shimmer (larger)
+ Container(
+ width: 60.w,
+ height: 32.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 12.h),
+ // Bottom row with chip and arrow
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Container(
+ width: 60.w,
+ height: 20.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ ).toShimmer(),
+ Container(
+ width: 16.w,
+ height: 16.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(2.r),
+ ),
+ ).toShimmer(),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // Build pages with 2 cards each
+ List _buildVitalSignPages({
+ required VitalSignResModel vitalSign,
+ required VoidCallback onTap,
+ required GlobalKey measureKey,
+ required int currentPageIndex,
+ }) {
+ return [
+ // Page 1: BMI + Height
+ Row(
+ children: [
+ Expanded(
+ child: _buildVitalSignCard(
+ icon: AppAssets.bmiVital,
+ label: "BMI",
+ value: vitalSign.bodyMassIndex?.toString() ?? '--',
+ unit: '',
+ status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null,
+ onTap: onTap,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: _buildVitalSignCard(
+ icon: AppAssets.heightVital,
+ label: "Height",
+ value: vitalSign.heightCm?.toString() ?? '--',
+ unit: 'cm',
+ status: null,
+ onTap: onTap,
+ ),
+ ),
+ ],
+ ),
+ // Page 2: Weight + Blood Pressure
+ Row(
+ children: [
+ Expanded(
+ child: _buildVitalSignCard(
+ icon: AppAssets.weightVital,
+ label: "Weight",
+ value: vitalSign.weightKg?.toString() ?? '--',
+ unit: 'kg',
+ status: vitalSign.weightKg != null ? "Normal" : null,
+ onTap: onTap,
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: _buildVitalSignCard(
+ icon: AppAssets.bloodPressure,
+ label: "Blood Pressure",
+ value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null
+ ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
+ : '--',
+ unit: '',
+ status: _getBloodPressureStatus(
+ systolic: vitalSign.bloodPressureHigher,
+ diastolic: vitalSign.bloodPressureLower,
+ ),
+ onTap: onTap,
+ ),
+ ),
+ ],
+ ),
+ ];
+ }
+
+ String _getBMIStatus(dynamic bmi) {
+ return VitalSignUiModel.bmiStatus(bmi);
+ }
+
+ String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
+ return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
+ }
+
+ Widget _buildVitalSignCard({
+ required String icon,
+ required String label,
+ required String value,
+ required String unit,
+ required String? status,
+ required VoidCallback onTap,
+ }) {
+ final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
+
+ return GestureDetector(
+ onTap: onTap,
+ child: Container(
+ // Same styling used originally for vitals in MedicalFilePage
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 16.r,
+ hasShadow: false,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(
+ padding: EdgeInsets.all(10.h),
+ decoration: BoxDecoration(
+ color: scheme.iconBg,
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Utils.buildSvgWithAssets(
+ icon: icon,
+ width: 20.w,
+ height: 20.h,
+ iconColor: scheme.iconFg,
+ fit: BoxFit.contain,
+ ),
+ ),
+ SizedBox(width: 10.w),
+ Expanded(
+ child: label.toText14(
+ color: AppColors.textColor,
+ weight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 14.h),
+
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
+ decoration: BoxDecoration(
+ color: AppColors.bgScaffoldColor,
+ borderRadius: BorderRadius.circular(10.r),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ value.toText17(
+ isBold: true,
+ color: AppColors.textColor,
+ ),
+ if (unit.isNotEmpty) ...[
+ SizedBox(width: 3.w),
+ unit.toText12(
+ color: AppColors.textColor,
+ fontWeight: FontWeight.w500,
+ ),
+ ],
+ ],
+ ),
+ if (status != null)
+ AppCustomChipWidget(
+ labelText: status,
+ backgroundColor: scheme.chipBg,
+ textColor: scheme.chipFg,
+ )
+ else
+ const SizedBox.shrink(),
+ ],
+ ),
+ ),
+ SizedBox(height: 8.h),
+
+ Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.arrow_forward,
+ width: 18.w,
+ height: 18.h,
+ iconColor: AppColors.textColorLight,
+ fit: BoxFit.contain,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
}
+
+
+
diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart
index cb78479..f6d7576 100644
--- a/lib/presentation/medical_report/medical_reports_page.dart
+++ b/lib/presentation/medical_report/medical_reports_page.dart
@@ -84,7 +84,7 @@ class _MedicalReportsPageState extends State {
// ),
// ],
// ).paddingSymmetrical(24.h, 0.h),
- SizedBox(height: 20.h),
+ SizedBox(height: 8.h),
Row(
children: [
CustomButton(
@@ -162,7 +162,7 @@ class _MedicalReportsPageState extends State {
patientMedicalReportResponseModel: PatientMedicalReportResponseModel(),
medicalFileViewModel: medicalFileVM,
isLoading: true,
- ).paddingSymmetrical(24.h, 8.h)
+ ).paddingSymmetrical(0.h, 8.h)
: medicalFileViewModel.patientMedicalReportList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart
new file mode 100644
index 0000000..cccd671
--- /dev/null
+++ b/lib/presentation/my_invoices/my_invoices_details_page.dart
@@ -0,0 +1,272 @@
+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/app_state.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/date_util.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
+import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:provider/provider.dart';
+
+class MyInvoicesDetailsPage extends StatefulWidget {
+ GetInvoiceDetailsResponseModel getInvoiceDetailsResponseModel;
+
+ MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel});
+
+ @override
+ State createState() => _MyInvoicesDetailsPageState();
+}
+
+class _MyInvoicesDetailsPageState extends State {
+ late MyInvoicesViewModel myInvoicesViewModel;
+
+ @override
+ Widget build(BuildContext context) {
+ myInvoicesViewModel = Provider.of(context, listen: false);
+ return Scaffold(
+ backgroundColor: AppColors.bgScaffoldColor,
+ body: Column(
+ children: [
+ Expanded(
+ child: CollapsingListView(
+ title: "Invoice Details".needTranslation,
+ sendEmail: () async {
+ LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation);
+ await myInvoicesViewModel.sendInvoiceEmail(
+ appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!,
+ projectID: widget.getInvoiceDetailsResponseModel.projectID!,
+ onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ },
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: EdgeInsets.all(24.h),
+ child: Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Column(
+ children: [
+ Image.network(
+ widget.getInvoiceDetailsResponseModel.doctorImageURL!,
+ width: 63.h,
+ height: 63.h,
+ fit: BoxFit.cover,
+ ).circle(100.r),
+ ],
+ ),
+ SizedBox(width: 16.w),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ (getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.doctorNameN! : widget.getInvoiceDetailsResponseModel.doctorName!).toText16(isBold: true),
+ SizedBox(height: 8.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 6.w,
+ runSpacing: 6.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.invoiceNo}: ${widget.getInvoiceDetailsResponseModel.invoiceNo!}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelText: (widget.getInvoiceDetailsResponseModel.clinicDescription!.length > 15
+ ? '${widget.getInvoiceDetailsResponseModel.clinicDescription!.substring(0, 12)}...'
+ : widget.getInvoiceDetailsResponseModel.clinicDescription!),
+ labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
+ ),
+ AppCustomChipWidget(
+ labelText: widget.getInvoiceDetailsResponseModel.projectName!,
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
+ icon: AppAssets.doctor_calendar_icon,
+ labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true),
+ SizedBox(height: 16.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 6.w,
+ runSpacing: 6.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ "Insurance Details".toText16(isBold: true),
+ SizedBox(height: 16.h),
+ widget.getInvoiceDetailsResponseModel.groupName!.toText14(isBold: true),
+ Row(
+ children: [
+ Expanded(child: widget.getInvoiceDetailsResponseModel.companyName!.toText14(isBold: true)),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Row(
+ children: [
+ AppCustomChipWidget(
+ labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.h,
+ hasShadow: false,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 24.h),
+ "Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
+ SizedBox(height: 17.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "Amount before tax".needTranslation.toText14(isBold: true),
+ Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13,
+ isSaudiCurrency: true),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
+ Utils.getPaymentAmountWithSymbol(
+ widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
+ isSaudiCurrency: true),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ SizedBox(height: 16.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "Discount".needTranslation.toText14(isBold: true),
+ Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor),
+ AppColors.primaryRedColor, 13,
+ isSaudiCurrency: true),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "Paid".needTranslation.toText14(isBold: true),
+ Utils.getPaymentAmountWithSymbol(
+ widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13,
+ isSaudiCurrency: true),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ SizedBox(height: 24.h),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart
new file mode 100644
index 0000000..ef1a9c2
--- /dev/null
+++ b/lib/presentation/my_invoices/my_invoices_list.dart
@@ -0,0 +1,116 @@
+import 'dart:async';
+
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_staggered_animations/flutter_staggered_animations.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/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
+import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
+import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart';
+import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
+import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
+import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:provider/provider.dart';
+
+class MyInvoicesList extends StatefulWidget {
+ const MyInvoicesList({super.key});
+
+ @override
+ State createState() => _MyInvoicesListState();
+}
+
+class _MyInvoicesListState extends State {
+ late MyInvoicesViewModel myInvoicesViewModel;
+
+ @override
+ void initState() {
+ scheduleMicrotask(() {
+ myInvoicesViewModel.setInvoicesListLoading();
+ myInvoicesViewModel.getAllInvoicesList();
+ });
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ myInvoicesViewModel = Provider.of(context, listen: false);
+ return CollapsingListView(
+ title: LocaleKeys.invoiceList.tr(context: context),
+ child: SingleChildScrollView(
+ child: Consumer(builder: (context, myInvoicesVM, child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 24.h),
+ ListView.builder(
+ itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.length,
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ padding: EdgeInsetsGeometry.zero,
+ itemBuilder: (context, index) {
+ return myInvoicesVM.isInvoicesListLoading
+ ? 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: InvoiceListCard(
+ getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
+ onTap: () async {
+ myInvoicesVM.setInvoiceDetailLoading();
+ LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation);
+ await myInvoicesVM.getInvoiceDetails(
+ appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
+ invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
+ projectID: myInvoicesVM.allInvoicesList[index].projectID!,
+ onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel),
+ ),
+ );
+ },
+ onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ }).paddingSymmetrical(24.w, 0.h),
+ ],
+ );
+ }),
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart
new file mode 100644
index 0000000..27ca79a
--- /dev/null
+++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart
@@ -0,0 +1,151 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/date_util.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+
+class InvoiceListCard extends StatelessWidget {
+ final GetInvoicesListResponseModel getInvoicesListResponseModel;
+ Function? onTap;
+
+ InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Wrap(
+ alignment: WrapAlignment.start,
+ direction: Axis.horizontal,
+ spacing: 6.w,
+ runSpacing: 6.h,
+ children: [
+ AppCustomChipWidget(
+ icon: AppAssets.walkin_appointment_icon,
+ iconColor: AppColors.textColor,
+ labelText: 'Walk In'.needTranslation,
+ textColor: AppColors.textColor,
+ ),
+ AppCustomChipWidget(
+ labelText: 'OutPatient'.needTranslation,
+ backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
+ textColor: AppColors.primaryRedColor,
+ ),
+ ],
+ ),
+ SizedBox(height: 16.h),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Column(
+ children: [
+ Image.network(
+ getInvoicesListResponseModel.doctorImageURL!,
+ width: 63.h,
+ height: 63.h,
+ fit: BoxFit.cover,
+ ).circle(100.r),
+ 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),
+ "${getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
+ ],
+ ),
+ ).circle(100),
+ ),
+ ],
+ ),
+ SizedBox(width: 16.w),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ (getIt().isArabic() ? getInvoicesListResponseModel.doctorNameN! : getInvoicesListResponseModel.doctorName!).toText16(isBold: true),
+ SizedBox(height: 8.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 6.w,
+ runSpacing: 6.h,
+ children: [
+ AppCustomChipWidget(
+ labelText: "${LocaleKeys.invoiceNo}: ${getInvoicesListResponseModel.invoiceNo!}",
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelText:
+ (getInvoicesListResponseModel.clinicName!.length > 15 ? '${getInvoicesListResponseModel.clinicName!.substring(0, 12)}...' : getInvoicesListResponseModel.clinicName!),
+ labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
+ ),
+ AppCustomChipWidget(
+ labelText: getInvoicesListResponseModel.projectName!,
+ labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
+ ),
+ AppCustomChipWidget(
+ labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
+ icon: AppAssets.doctor_calendar_icon,
+ labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 16.h),
+ CustomButton(
+ text: "View invoice details".needTranslation,
+ onPressed: () {
+ if (onTap != null) {
+ onTap!();
+ }
+ },
+ backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
+ borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14.f,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12.r,
+ padding: EdgeInsets.symmetric(horizontal: 10.w),
+ height: 40.h,
+ iconSize: 14.h,
+ ),
+ ],
+ ),
+ ),
+ ).paddingOnly(bottom: 16.h);
+ }
+}
diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart
index 265559b..a40a27b 100644
--- a/lib/presentation/onboarding/onboarding_screen.dart
+++ b/lib/presentation/onboarding/onboarding_screen.dart
@@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:lottie/lottie.dart';
@@ -30,7 +31,13 @@ class _OnboardingScreenState extends State {
void goToHomePage() {
Utils.saveBoolFromPrefs(CacheConst.firstLaunch, false);
- Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation()));
+ Navigator.pushAndRemoveUntil(
+ context,
+ CustomPageRoute(
+ page: LandingNavigation(),
+ ),
+ (r) => false);
+ // Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation()));
}
@override
diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart
index a38ff92..a6c6965 100644
--- a/lib/presentation/prescriptions/prescription_detail_page.dart
+++ b/lib/presentation/prescriptions/prescription_detail_page.dart
@@ -219,7 +219,15 @@ class _PrescriptionDetailPageState extends State {
text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!
? LocaleKeys.resendOrder.tr(context: context)
: LocaleKeys.prescriptionDeliveryError.tr(context: context),
- onPressed: () {},
+ onPressed: () async {
+ if (widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!) {
+ LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation);
+ await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) {
+ LoaderBottomSheet.hideLoader();
+ prescriptionsViewModel.initiatePrescriptionDelivery();
+ });
+ }
+ },
backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color,
borderColor: AppColors.successColor.withOpacity(0.01),
textColor:
diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart
index c0eb782..fb153ea 100644
--- a/lib/presentation/radiology/radiology_orders_page.dart
+++ b/lib/presentation/radiology/radiology_orders_page.dart
@@ -190,231 +190,173 @@ class _RadiologyOrdersPageState extends State {
itemBuilder: (context, index) {
final group = model.patientRadiologyOrdersViewList[index];
final displayName = model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
+ final isExpanded = expandedIndex == index;
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 400),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // Group header container with key so we can scroll to it
- GestureDetector(
- onTap: () {
- setState(() {
- expandedIndex = expandedIndex == index ? null : index;
- });
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
- if (key.currentContext != null && expandedIndex == index) {
- // Delay scrolling to wait for expansion animation
- Future.delayed(Duration(milliseconds: 450), () {
- if (key.currentContext != null) {
- Scrollable.ensureVisible(
- key.currentContext!,
- duration: Duration(milliseconds: 350),
- curve: Curves.easeInOut,
- alignment: 0.0,
- );
- }
- });
- }
- });
- },
- child: Container(
- key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
- margin: EdgeInsets.only(bottom: 8.h),
- padding: EdgeInsets.all(12.h),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(8.h),
- boxShadow: [
- BoxShadow(
- color: AppColors.blackColor.withValues(alpha: 0.03),
- blurRadius: 6,
- offset: Offset(0, 2),
- )
- ],
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
+ if (key.currentContext != null && expandedIndex == index) {
+ Future.delayed(const Duration(milliseconds: 450), () {
+ if (key.currentContext != null) {
+ Scrollable.ensureVisible(
+ key.currentContext!,
+ duration: const Duration(milliseconds: 350),
+ curve: Curves.easeInOut,
+ alignment: 0.0,
+ );
+ }
+ });
+ }
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- CustomButton(
- text: "${group.length} ${'results'.needTranslation}",
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 30.h,
- ),
- Icon(expandedIndex == index ? Icons.expand_less : Icons.expand_more),
- ],
- ),
- SizedBox(height: 8.h),
- Text(
- displayName,
- style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
- overflow: TextOverflow.ellipsis,
- ),
+ AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
- ),
- ],
+ SizedBox(height: 8.h),
+ Text(
+ displayName,
+ style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
),
- ),
- ),
- AnimatedSwitcher(
- duration: Duration(milliseconds: 400),
- child: expandedIndex == index
- ? Container(
- key: ValueKey(index),
- padding: EdgeInsets.only(top: 12.h),
- child: Column(
- children: group.map((order) {
- return Container(
- margin: EdgeInsets.only(bottom: 12.h),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 12.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Image.network(
- order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
- width: 24.h,
- height: 24.h,
- fit: BoxFit.cover,
- ).circle(100),
- SizedBox(width: 8.h),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
-
- children: [
- (order.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
- SizedBox(height: 6.h),
-
-
- ],
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...group.map((order) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Image.network(
+ order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
+ width: 24.w,
+ height: 24.h,
+ fit: BoxFit.cover,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(
+ child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500),
),
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(children: [
- CustomButton(
- text: order.description!,
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- )
-
- ]
- ),
- SizedBox(height: 6.h),
-
- Row(
- children: [
- CustomButton(
- text: DateUtil.formatDateToDate(order.orderDate ?? order.appointmentDate ?? "", false),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- ),
- SizedBox(width: 8.h),
- CustomButton(
- text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(
- children: [
- Expanded(
- flex: 6,
- child: SizedBox(),
- ),
- SizedBox(width: 8.h),
- Expanded(
- flex: 1,
- child: Container(
- height: 40.h,
- width: 40.w,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.textColor,
- borderRadius: 12,
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 4.h,
+ runSpacing: 4.h,
+ children: [
+ if ((order.description ?? '').isNotEmpty)
+ AppCustomChipWidget(
+ labelText: (order.description ?? '').toString(),
),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Transform.flip(
- flipX: false,
- child: Utils.buildSvgWithAssets(
- icon: AppAssets.forward_arrow_icon_small,
- iconColor: AppColors.whiteColor,
- fit: BoxFit.contain,
- ),
- ),
+ AppCustomChipWidget(
+ labelText: DateUtil.formatDateToDate(
+ (order.orderDate ?? order.appointmentDate),
+ false,
),
- ).onPress(() {
- model.navigationService.push(
- CustomPageRoute(
- page: RadiologyResultPage(patientRadiologyResponseModel: order),
- ),
- );
- }),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }).toList(),
- ),
- )
- : SizedBox.shrink(),
+ ),
+ AppCustomChipWidget(
+ labelText: model.isSortByClinic ? (order.clinicDescription ?? '') : (order.projectName ?? ''),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Row(
+ children: [
+ Expanded(flex: 2, child: const SizedBox()),
+ Expanded(
+ flex: 2,
+ child: CustomButton(
+ icon: AppAssets.view_report_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
+ text: "View Results".needTranslation,
+ onPressed: () {
+ model.navigationService.push(
+ CustomPageRoute(
+ page: RadiologyResultPage(patientRadiologyResponseModel: order),
+ ),
+ );
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12,
+ padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
+ ],
+ );
+ }).toList(),
+ ],
+ ),
+ )
+ : const SizedBox.shrink(),
+ ),
+ ],
),
- ],
+ ),
),
),
),
diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart
index e5163f5..c3ce68b 100644
--- a/lib/presentation/symptoms_checker/user_info_selection.dart
+++ b/lib/presentation/symptoms_checker/user_info_selection.dart
@@ -3,7 +3,6 @@ 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/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@@ -18,8 +17,7 @@ class UserInfoSelectionScreen extends StatefulWidget {
const UserInfoSelectionScreen({super.key});
@override
- State createState() =>
- _UserInfoSelectionScreenState();
+ State createState() => _UserInfoSelectionScreenState();
}
class _UserInfoSelectionScreenState extends State {
@@ -53,7 +51,7 @@ class _UserInfoSelectionScreenState extends State {
if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
try {
- DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
+ DateTime dob = DateTime.parse(user.dateofBirth!);
viewModel.setDateOfBirth(dob);
} catch (e) {
// If date parsing fails, ignore and let user fill manually
@@ -87,25 +85,19 @@ class _UserInfoSelectionScreenState extends State {
width: 40.h,
margin: EdgeInsets.only(right: 10.h),
padding: EdgeInsets.all(8.h),
- decoration: RoundedRectangleBorder()
- .toSmoothCornerDecoration(
- borderRadius: 12.r, color: AppColors.greyColor),
- child: Utils.buildSvgWithAssets(
- icon: leadingIcon, iconColor: iconColor)),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor),
+ child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(weight: FontWeight.w500),
- subTitle.toText14(
- color: AppColors.primaryRedColor,
- weight: FontWeight.w500),
+ subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
],
),
],
),
),
- Utils.buildSvgWithAssets(
- icon: trailingIcon, height: 24.h, width: 24.h),
+ Utils.buildSvgWithAssets(icon: trailingIcon, height: 24.h, width: 24.h),
],
),
);
@@ -122,27 +114,8 @@ class _UserInfoSelectionScreenState extends State {
AppState appState = getIt.get();
String name = "";
- int? userAgeFromDOB;
-
if (appState.isAuthenticated) {
- final user = appState.getAuthenticatedUser();
- name = "${user!.firstName!} ${user.lastName!} ";
-
- // Calculate age from authenticated user's DOB if available
- if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
- try {
- DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
- final now = DateTime.now();
- int age = now.year - dob.year;
- if (now.month < dob.month ||
- (now.month == dob.month && now.day < dob.day)) {
- age--;
- }
- userAgeFromDOB = age;
- } catch (e) {
- // If date parsing fails, ignore
- }
- }
+ name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} ";
} else {
name = "Guest";
}
@@ -159,15 +132,12 @@ class _UserInfoSelectionScreenState extends State {
// Get display values
String genderText = viewModel.selectedGender ?? "Not set";
- // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB)
- int? displayAge = viewModel.selectedAge ?? userAgeFromDOB;
- String ageText = displayAge != null ? "$displayAge Years" : "Not set";
- String heightText = viewModel.selectedHeight != null
- ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}"
- : "Not set";
- String weightText = viewModel.selectedWeight != null
- ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}"
- : "Not set";
+ // Show age calculated from DOB, not the DOB itself
+ String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set";
+ String heightText =
+ viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set";
+ String weightText =
+ viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : "Not set";
return Column(
children: [
@@ -180,17 +150,11 @@ class _UserInfoSelectionScreenState extends State {
children: [
Container(
width: double.infinity,
- decoration: RoundedRectangleBorder()
- .toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 24.r),
- padding: EdgeInsets.symmetric(
- vertical: 24.h, horizontal: 16.w),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
+ padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
children: [
- "Hello $name, Is your information up to date?"
- .needTranslation
- .toText18(
+ "Hello $name, Is your information up to date?".needTranslation.toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
),
@@ -201,10 +165,8 @@ class _UserInfoSelectionScreenState extends State {
title: "Gender".needTranslation,
subTitle: genderText,
onTap: () {
- viewModel.setUserInfoPage(0,
- isSinglePageEdit: true);
- context.navigateWithName(
- AppRoutes.userInfoFlowManager);
+ viewModel.setUserInfoPage(0, isSinglePageEdit: true);
+ context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@@ -216,10 +178,8 @@ class _UserInfoSelectionScreenState extends State {
subTitle: ageText,
iconColor: AppColors.greyTextColor,
onTap: () {
- viewModel.setUserInfoPage(1,
- isSinglePageEdit: true);
- context.navigateWithName(
- AppRoutes.userInfoFlowManager);
+ viewModel.setUserInfoPage(1, isSinglePageEdit: true);
+ context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@@ -230,10 +190,8 @@ class _UserInfoSelectionScreenState extends State {
title: "Height".needTranslation,
subTitle: heightText,
onTap: () {
- viewModel.setUserInfoPage(2,
- isSinglePageEdit: true);
- context.navigateWithName(
- AppRoutes.userInfoFlowManager);
+ viewModel.setUserInfoPage(2, isSinglePageEdit: true);
+ context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@@ -244,10 +202,8 @@ class _UserInfoSelectionScreenState extends State {
title: "Weight".needTranslation,
subTitle: weightText,
onTap: () {
- viewModel.setUserInfoPage(3,
- isSinglePageEdit: true);
- context.navigateWithName(
- AppRoutes.userInfoFlowManager);
+ viewModel.setUserInfoPage(3, isSinglePageEdit: true);
+ context.navigateWithName(AppRoutes.userInfoFlowManager);
},
trailingIcon: AppAssets.edit_icon,
),
@@ -269,8 +225,7 @@ class _UserInfoSelectionScreenState extends State {
Widget _buildBottomCard(BuildContext context, bool hasEmptyFields) {
return Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor, borderRadius: 24.r),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: SafeArea(
top: false,
child: Column(
diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart
index 8907fc8..0d2d806 100644
--- a/lib/presentation/todo_section/todo_page.dart
+++ b/lib/presentation/todo_section/todo_page.dart
@@ -47,6 +47,7 @@ class _ToDoPageState extends State {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 3,
+ padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return AncillaryOrderCard(
order: AncillaryOrderItem(),
@@ -60,15 +61,13 @@ class _ToDoPageState extends State {
Widget build(BuildContext context) {
appState = getIt.get();
return CollapsingListView(
- title: "ToDo List".needTranslation,
- isLeading: false,
+ title: "Ancillary Orders".needTranslation,
+ isLeading: true,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
- "Ancillary Orders".needTranslation.toText18(isBold: true),
- SizedBox(height: 24.h),
Consumer(
builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) {
return todoSectionViewModel.isAncillaryOrdersLoading
diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart
index 8a3e3fa..910da59 100644
--- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart
+++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart
@@ -7,9 +7,13 @@ 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/todo_section/models/resp_models/ancillary_order_list_response_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
+import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
+import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class AncillaryOrdersList extends StatelessWidget {
final List orders;
@@ -52,6 +56,7 @@ class AncillaryOrdersList extends StatelessWidget {
child: AncillaryOrderCard(
order: order,
isLoading: false,
+ isOrdersList: true,
onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null,
onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null,
)),
@@ -90,12 +95,14 @@ class AncillaryOrderCard extends StatelessWidget {
super.key,
required this.order,
this.isLoading = false,
+ this.isOrdersList = false,
this.onCheckIn,
this.onViewDetails,
});
final AncillaryOrderItem order;
final bool isLoading;
+ final bool isOrdersList;
final VoidCallback? onCheckIn;
final VoidCallback? onViewDetails;
@@ -113,30 +120,24 @@ class AncillaryOrderCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Header Row with Order Number and Date
- // Row(
- // mainAxisAlignment: MainAxisAlignment.spaceBetween,
- // children: [
- // Row(
- // children: [
- // if (!isLoading)
- // "Order #".needTranslation.toText14(
- // color: AppColors.textColorLight,
- // weight: FontWeight.w500,
- // ),
- // SizedBox(width: 4.w),
- // (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading),
- // ],
- // ),
- // if (order.orderDate != null || isLoading)
- // (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!))
- // .toText12(color: AppColors.textColorLight)
- // .toShimmer2(isShow: isLoading),
- // ],
- // ),
-
- SizedBox(height: 12.h),
-
+ isOrdersList
+ ? SizedBox.shrink()
+ : Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "Ancillary Orders".toText14(isBold: true),
+ Row(
+ children: [
+ LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
+ SizedBox(width: 2.h),
+ Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
+ ],
+ ),
+ ],
+ ).toShimmer2(isShow: isLoading).onPress(() {
+ Navigator.of(context).push(CustomPageRoute(page: ToDoPage()));
+ }),
+ SizedBox(height: 10.h),
// Doctor and Clinic Info
Row(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -144,8 +145,8 @@ class AncillaryOrderCard extends StatelessWidget {
if (!isLoading) ...[
Image.network(
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png",
- width: 40.w,
- height: 40.h,
+ width: 63.w,
+ height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 12.w),
@@ -155,11 +156,7 @@ class AncillaryOrderCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Doctor Name
- if (order.doctorName != null || isLoading)
- (isLoading ? "Dr. John Smith" : order.doctorName!)
- .toString()
- .toText14(isBold: true, maxlines: 2)
- .toShimmer2(isShow: isLoading),
+ if (order.doctorName != null || isLoading) (isLoading ? "Dr. John Smith" : order.doctorName!).toString().toText16(isBold: true, maxlines: 2).toShimmer2(isShow: isLoading),
SizedBox(height: 4.h),
],
@@ -167,9 +164,7 @@ class AncillaryOrderCard extends StatelessWidget {
),
],
),
-
- SizedBox(height: 12.h),
-
+ SizedBox(height: 8.h),
// Chips for Appointment Info and Status
Wrap(
direction: Axis.horizontal,
@@ -191,9 +186,8 @@ class AncillaryOrderCard extends StatelessWidget {
// Appointment Date
if (order.appointmentDate != null || isLoading)
AppCustomChipWidget(
- icon: AppAssets.calendar,
- labelText:
- isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation,
+ icon: AppAssets.appointment_calendar_icon,
+ labelText: isLoading ? "Date: Jan 20, 2024" : DateFormat('MMM dd, yyyy').format(order.appointmentDate!).needTranslation,
).toShimmer2(isShow: isLoading),
// Appointment Number
diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart
index 858e801..bd25641 100644
--- a/lib/presentation/vital_sign/vital_sign_page.dart
+++ b/lib/presentation/vital_sign/vital_sign_page.dart
@@ -1,4 +1,4 @@
-import 'dart:async';
+import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vit
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
-import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart';
import 'package:provider/provider.dart';
class VitalSignPage extends StatefulWidget {
@@ -22,22 +22,10 @@ class VitalSignPage extends StatefulWidget {
}
class _VitalSignPageState extends State {
+
@override
void initState() {
super.initState();
- final HmgServicesViewModel hmgServicesViewModel = context.read();
-
- scheduleMicrotask(() async {
- LoaderBottomSheet.showLoader(loadingText: 'Loading Vital Signs...');
- await hmgServicesViewModel.getPatientVitalSign(
- onSuccess: (_) {
- LoaderBottomSheet.hideLoader();
- },
- onError: (_) {
- LoaderBottomSheet.hideLoader();
- },
- );
- });
}
@override
@@ -72,48 +60,40 @@ class _VitalSignPageState extends State {
children: [
// BMI Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
+ icon: AppAssets.bmiVital,
label: 'BMI',
value: latestVitalSign?.bodyMassIndex?.toString() ?? '--',
unit: '',
- chipText: _getBMIStatus(latestVitalSign?.bodyMassIndex),
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex),
+ onTap: () {},
),
SizedBox(height: 16.h),
// Height Card
_buildVitalSignCard(
- icon: AppAssets.height,
- iconColor: AppColors.infoColor,
- iconBgColor: AppColors.infoColor.withValues(alpha: 0.1),
+ icon: AppAssets.heightVital,
label: 'Height',
value: latestVitalSign?.heightCm?.toString() ?? '--',
unit: 'cm',
+ status: null,
+ onTap: () {},
),
SizedBox(height: 16.h),
// Weight Card
_buildVitalSignCard(
- icon: AppAssets.weight,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
+ icon: AppAssets.weightVital,
label: 'Weight',
value: latestVitalSign?.weightKg?.toString() ?? '--',
unit: 'kg',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ status: (latestVitalSign?.weightKg != null) ? 'Normal' : null,
+ onTap: () {},
),
SizedBox(height: 16.h),
// Blood Pressure Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.warningColor,
- iconBgColor: AppColors.warningColor.withValues(alpha: 0.1),
+ icon: AppAssets.bloodPressure,
label: 'Blood Pressure',
value: latestVitalSign != null &&
latestVitalSign.bloodPressureHigher != null &&
@@ -121,17 +101,22 @@ class _VitalSignPageState extends State {
? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}'
: '--',
unit: '',
+ status: VitalSignUiModel.bloodPressureStatus(
+ systolic: latestVitalSign?.bloodPressureHigher,
+ diastolic: latestVitalSign?.bloodPressureLower,
+ ),
+ onTap: () {},
),
SizedBox(height: 16.h),
// Temperature Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.errorColor,
- iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
+ icon: AppAssets.temperature,
label: 'Temperature',
value: latestVitalSign?.temperatureCelcius?.toString() ?? '--',
unit: '°C',
+ status: null,
+ onTap: () {},
),
],
),
@@ -143,54 +128,80 @@ class _VitalSignPageState extends State {
Expanded(
child: Column(
children: [
- // Body anatomy image
- Container(
- height: 280.h,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Center(
- child: Image.asset(
- AppAssets.fullBodyFront,
- height: 260.h,
- fit: BoxFit.contain,
- ),
- ),
- ),
- SizedBox(height: 16.h),
+ // Body anatomy image with Heart Rate card overlaid at bottom
+ SizedBox(
+ height: 480.h,
+ width: double.infinity,
+ child: Stack(
+ clipBehavior: Clip.none,
+ children: [
+ // Image
+ Positioned.fill(
+ child: Stack(
+ fit: StackFit.expand,
+ children: [
+ Image.asset(
+ AppAssets.bmiFullBody,
+ fit: BoxFit.cover,
+ alignment: Alignment.topCenter,
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: SizedBox(
+ height: 420.h,
+ child: ImageFiltered(
+ imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ AppColors.whiteColor.withValues(alpha: 0.0),
+ AppColors.whiteColor.withValues(alpha: 0.97),
+ AppColors.whiteColor,
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
- // Heart Rate Card
- _buildVitalSignCard(
- icon: AppAssets.heart,
- iconColor: AppColors.errorColor,
- iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
- label: 'Heart Rate',
- value: latestVitalSign?.heartRate?.toString() ??
- latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
- unit: 'bpm',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ // Overlay Heart Rate card
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 12.h,
+ child: _buildVitalSignCard(
+ icon: AppAssets.heart,
+ label: 'Heart Rate',
+ value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
+ unit: 'bpm',
+ status: 'Normal',
+ onTap: () {},
+ ),
+ ),
+ ],
+ ),
),
- SizedBox(height: 16.h),
+ SizedBox(height: 12.h),
- // Respiratory rate Card
- _buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
- label: 'Respiratory rate',
- value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
- unit: 'bpm',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
- ),
- ],
- ),
- ),
+ // Respiratory rate Card
+ _buildVitalSignCard(
+ icon: AppAssets.respRate,
+ label: 'Respiratory rate',
+ value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
+ unit: 'bpm',
+ status: 'Normal',
+ onTap: () {},
+ ),
+ ],
+ ),
+ ),
],
),
),
@@ -205,104 +216,106 @@ class _VitalSignPageState extends State {
);
}
- String? _getBMIStatus(dynamic bmi) {
- if (bmi == null) return null;
- double bmiValue = double.tryParse(bmi.toString()) ?? 0;
- if (bmiValue < 18.5) return 'Underweight';
- if (bmiValue < 25) return 'Normal';
- if (bmiValue < 30) return 'Overweight';
- return 'Obese';
- }
-
Widget _buildVitalSignCard({
required String icon,
- required Color iconColor,
- required Color iconBgColor,
required String label,
required String value,
required String unit,
- String? chipText,
- Color? chipBgColor,
- Color? chipTextColor,
+ required String? status,
+ required VoidCallback onTap,
}) {
- return Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- // Icon with background
- Container(
- padding: EdgeInsets.all(8.h),
- decoration: BoxDecoration(
- color: iconBgColor,
- borderRadius: BorderRadius.circular(12.r),
+ final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
+
+ return GestureDetector(
+ onTap: onTap,
+ child: Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 16.r,
+ hasShadow: false,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(
+ padding: EdgeInsets.all(10.h),
+ decoration: BoxDecoration(
+ color: scheme.iconBg,
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Utils.buildSvgWithAssets(
+ icon: icon,
+ width: 20.w,
+ height: 20.h,
+ iconColor: scheme.iconFg,
+ fit: BoxFit.contain,
+ ),
),
- child: Utils.buildSvgWithAssets(
- icon: icon,
- width: 16.w,
- height: 16.h,
- iconColor: iconColor,
- fit: BoxFit.contain,
+ SizedBox(width: 10.w),
+ Expanded(
+ child: label.toText14(
+ color: AppColors.textColor,
+ weight: FontWeight.w600,
+ ),
),
+ ],
+ ),
+ SizedBox(height: 14.h),
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
+ decoration: BoxDecoration(
+ color: AppColors.bgScaffoldColor,
+ borderRadius: BorderRadius.circular(10.r),
),
- SizedBox(width: 8.w),
- Expanded(
- child: label.toText10(
- color: AppColors.textColorLight,
- weight: FontWeight.w500,
- ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ value.toText17(
+ isBold: true,
+ color: AppColors.textColor,
+ ),
+ if (unit.isNotEmpty) ...[
+ SizedBox(width: 3.w),
+ unit.toText12(
+ color: AppColors.textColor,
+ fontWeight: FontWeight.w500,
+ ),
+ ],
+ ],
+ ),
+ if (status != null)
+ AppCustomChipWidget(
+ labelText: status,
+ backgroundColor: scheme.chipBg,
+ textColor: scheme.chipFg,
+ )
+ else
+ const SizedBox.shrink(),
+ ],
),
- // Forward arrow
- Utils.buildSvgWithAssets(
+ ),
+ SizedBox(height: 8.h),
+ Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
- width: 16.w,
- height: 16.h,
+ width: 18.w,
+ height: 18.h,
iconColor: AppColors.textColorLight,
fit: BoxFit.contain,
),
- ],
- ),
- SizedBox(height: 12.h),
-
- // Value
- Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- value.toText18(
- isBold: true,
- color: AppColors.textColor,
- ),
- if (unit.isNotEmpty) ...[
- SizedBox(width: 4.w),
- unit.toText12(
- color: AppColors.textColorLight,
- ),
- ],
- ],
- ),
-
- // Chip if available
- if (chipText != null) ...[
- SizedBox(height: 8.h),
- AppCustomChipWidget(
- labelText: chipText,
- backgroundColor: chipBgColor,
- textColor: chipTextColor,
- padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
),
],
- ],
+ ),
),
),
);
}
}
-
diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart
index c1ab6d4..aa6cc3e 100644
--- a/lib/widgets/appbar/collapsing_list_view.dart
+++ b/lib/widgets/appbar/collapsing_list_view.dart
@@ -21,6 +21,7 @@ class CollapsingListView extends StatelessWidget {
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
+ VoidCallback? sendEmail;
Widget? bottomChild;
Widget? trailing;
bool isClose;
@@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget {
this.history,
this.instructions,
this.requests,
+ this.sendEmail,
this.isLeading = true,
this.trailing,
this.leadingCallback,
@@ -78,6 +80,7 @@ class CollapsingListView extends StatelessWidget {
history: history,
instructions: instructions,
requests: requests,
+ sendEmail: sendEmail,
bottomChild: bottomChild,
trailing: trailing,
)
@@ -108,6 +111,7 @@ class CollapsingListView extends StatelessWidget {
history: history,
instructions: instructions,
requests: requests,
+ sendEmail: sendEmail,
bottomChild: bottomChild,
trailing: trailing,
),
@@ -181,6 +185,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
VoidCallback? history;
VoidCallback? instructions;
VoidCallback? requests;
+ VoidCallback? sendEmail;
Widget? bottomChild;
Widget? trailing;
@@ -195,6 +200,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
this.history,
this.instructions,
this.requests,
+ this.sendEmail,
this.bottomChild,
this.trailing,
});
@@ -267,6 +273,7 @@ class _ScrollAnimatedTitleState extends State {
if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!),
if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!),
if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!),
+ if (widget.sendEmail != null) actionButton(context, t, title: "Send Email".needTranslation, icon: AppAssets.email).onPress(widget.sendEmail!),
if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!),
if (widget.trailing != null) widget.trailing!,
]
diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart
index a3e117f..c820468 100644
--- a/lib/widgets/bottom_navigation/bottom_navigation.dart
+++ b/lib/widgets/bottom_navigation/bottom_navigation.dart
@@ -30,7 +30,8 @@ class BottomNavigation extends StatelessWidget {
isSpecial: true,
),
appState.isAuthenticated
- ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context))
+ // ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context))
+ ? BottomNavItem(icon: AppAssets.toDoBottom, label: "Symptoms")
: BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()),
BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr(context: context)),
];
@@ -39,7 +40,7 @@ class BottomNavigation extends StatelessWidget {
decoration: _containerDecoration,
padding: _containerPadding,
child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
items.length,
(index) => _buildNavItem(items[index], index),
diff --git a/lib/widgets/shimmer/vital_sign_shimmer_widget.dart b/lib/widgets/shimmer/vital_sign_shimmer_widget.dart
new file mode 100644
index 0000000..2ab4741
--- /dev/null
+++ b/lib/widgets/shimmer/vital_sign_shimmer_widget.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+class VitalSignShimmerWidget extends StatelessWidget {
+ const VitalSignShimmerWidget({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ // BMI Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Height Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Weight Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Blood Pressure Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ ],
+ );
+ }
+
+ Widget _buildShimmerCard() {
+ return Container(
+ decoration: BoxDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(12.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // Icon shimmer
+ Container(
+ width: 32.w,
+ height: 32.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 8.h),
+ // Label shimmer
+ Container(
+ width: 50.w,
+ height: 10.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Value shimmer
+ Container(
+ width: 40.w,
+ height: 16.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Chip shimmer
+ Container(
+ width: 45.w,
+ height: 18.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Arrow shimmer
+ Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: Container(
+ width: 10.w,
+ height: 10.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(2.r),
+ ),
+ ).toShimmer(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+