diff --git a/assets/images/png/home_health_care.png b/assets/images/png/home_health_care.png new file mode 100644 index 0000000..21378c4 Binary files /dev/null and b/assets/images/png/home_health_care.png differ diff --git a/assets/images/png/pharmacy_service.png b/assets/images/png/pharmacy_service.png new file mode 100644 index 0000000..7093d41 Binary files /dev/null and b/assets/images/png/pharmacy_service.png differ diff --git a/assets/images/svg/approximate_ovulation_accordion.svg b/assets/images/svg/approximate_ovulation_accordion.svg new file mode 100644 index 0000000..a721d61 --- /dev/null +++ b/assets/images/svg/approximate_ovulation_accordion.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/bloodcholestrol.svg b/assets/images/svg/bloodcholestrol.svg new file mode 100644 index 0000000..8a77bb5 --- /dev/null +++ b/assets/images/svg/bloodcholestrol.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/bloodsugar.svg b/assets/images/svg/bloodsugar.svg new file mode 100644 index 0000000..a97032c --- /dev/null +++ b/assets/images/svg/bloodsugar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/bulb.svg b/assets/images/svg/bulb.svg new file mode 100644 index 0000000..94553a5 --- /dev/null +++ b/assets/images/svg/bulb.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/due_date_accordion.svg b/assets/images/svg/due_date_accordion.svg new file mode 100644 index 0000000..828a1c9 --- /dev/null +++ b/assets/images/svg/due_date_accordion.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/fertile_window_accordion.svg b/assets/images/svg/fertile_window_accordion.svg new file mode 100644 index 0000000..63f0173 --- /dev/null +++ b/assets/images/svg/fertile_window_accordion.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/svg/next_period_accordion.svg b/assets/images/svg/next_period_accordion.svg new file mode 100644 index 0000000..4ff65db --- /dev/null +++ b/assets/images/svg/next_period_accordion.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/phramacy_icon.svg b/assets/images/svg/phramacy_icon.svg new file mode 100644 index 0000000..a9c5d1c --- /dev/null +++ b/assets/images/svg/phramacy_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/pregnancy_test_day_accordion.svg b/assets/images/svg/pregnancy_test_day_accordion.svg new file mode 100644 index 0000000..5a29588 --- /dev/null +++ b/assets/images/svg/pregnancy_test_day_accordion.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/switch.svg b/assets/images/svg/switch.svg new file mode 100644 index 0000000..1db8753 --- /dev/null +++ b/assets/images/svg/switch.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/triglycerides.svg b/assets/images/svg/triglycerides.svg new file mode 100644 index 0000000..0f8facd --- /dev/null +++ b/assets/images/svg/triglycerides.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 390e448..f0db308 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -454,8 +454,6 @@ var GET_ANCILLARY_ORDERS_DETAILS = 'Services/Doctors.svc/REST/GetOnlineAncillary //Pharmacy wishlist // var GET_WISHLIST = "http://swd-pharapp-01:7200/api/shopping_cart_items/"; -var GET_DOCTOR_LIST_BY_TIME = "Services/Doctors.svc/REST/SearchDoctorsByTime"; - // pharmacy var PHARMACY_AUTORZIE_CUSTOMER = "AutorizeCustomer"; var PHARMACY_VERIFY_CUSTOMER = "VerifyCustomer"; @@ -553,7 +551,6 @@ var GET_FINAL_PRODUCTS = 'products?fields=id,reviews,discount_ids,name,namen,localized_names,display_order,short_description,full_description,full_descriptionn,sku,order_minimum_quantity,order_maximum_quantity,price,old_price,images,is_rx,rx_message,rx_messagen,discount_name,discount_namen,approved_rating_sum,approved_total_reviews,allow_back_in_stock_subscriptions,stock_quantity,stock_availability,stock_availabilityn,discount_percentage&CategoryId='; var GET_CLINIC_CATEGORY = 'Services/Doctors.svc/REST/DP_GetClinicCategory'; var GET_DISEASE_BY_CLINIC_ID = 'Services/Doctors.svc/REST/DP_GetDiseasesByClinicID'; -var SEARCH_DOCTOR_BY_TIME = 'Services/Doctors.svc/REST/SearchDoctorsByTime'; var TIMER_MIN = 10; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 40add26..f85ac79 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -221,6 +221,7 @@ class AppAssets { static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg'; static const String trade_down_red = '$svgBasePath/trade_down_red.svg'; + static const String pharmacy_icon = '$svgBasePath/phramacy_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -246,9 +247,28 @@ class AppAssets { static const String refreshIcon = '$svgBasePath/refresh.svg'; static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg'; + + + static const String bloodSugar = '$svgBasePath/bloodsugar.svg'; + static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg'; + static const String triglycerides = '$svgBasePath/triglycerides.svg'; + static const String bulb = '$svgBasePath/bulb.svg'; + static const String switchBtn = '$svgBasePath/switch.svg'; + + + static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg'; + static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg'; + static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg'; + static const String pregnancyDayAccordion = '$svgBasePath/pregnancy_test_day_accordion.svg'; + static const String pregnancyDueDateAccordion = '$svgBasePath/due_date_accordion.svg'; + // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; static const String liveCareService = '$pngBasePath/livecare_service.png'; + + static const String homeHealthCareService = '$pngBasePath/home_health_care.png'; + static const String pharmacyService = '$pngBasePath/pharmacy_service.png'; + static const String maleImg = '$pngBasePath/male_img.png'; static const String femaleImg = '$pngBasePath/female_img.png'; static const String babyGirlImg = '$pngBasePath/baby_girl_img.png'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 8cc29df..2f45f37 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -63,6 +63,8 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + import '../features/active_prescriptions/active_prescriptions_repo.dart'; import '../features/monthly_reports/terms_conditions_repo.dart'; import '../features/monthly_reports/terms_conditions_view_model.dart'; @@ -148,15 +150,13 @@ class AppDependencies { () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), ); - getIt.registerLazySingleton( - () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); + getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); - getIt.registerLazySingleton( - () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel( diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 3a9effb..8fd4818 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -34,9 +34,11 @@ enum FamilyFileEnum { active, inactive, blocked, deleted, pending, rejected } enum BodyView { front, back } +enum HealthCalConEnum { calculator, converter } + enum HealthCalculatorEnum { general, women } -enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate } +enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides } extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { String get displayName { @@ -59,6 +61,12 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { return isArabic ? "فترة الإباضة" : "Ovulation Period"; case HealthCalculatorsTypeEnum.deliveryDueDate: return isArabic ? "تاريخ استحقاق التسليم" : "Delivery Due Date"; + case HealthCalculatorsTypeEnum.bloodSugar: + return isArabic ? "سكر الدم" : "Blood Sugar"; + case HealthCalculatorsTypeEnum.bloodCholesterol: + return isArabic ? "كوليسترول الدم" : "Blood Cholesterol"; + case HealthCalculatorsTypeEnum.triglycerides: + return isArabic ? "الدهون الثلاثية في الدم" : "Triglycerides Fat Blood"; } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 397adad..38d04b9 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -925,4 +925,11 @@ class Utils { return ""; } + static bool isDateToday(DateTime dateToCheck) { + final DateTime now = DateTime.now(); + final DateTime today = DateTime(now.year, now.month, now.day); + final DateTime checkDate = DateTime(dateToCheck.year, dateToCheck.month, dateToCheck.day); + + return checkDate == today; + } } diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 81cd060..b1a7757 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -3,8 +3,8 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; -import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/search_e_referral_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart'; @@ -24,8 +24,9 @@ class HmgServicesViewModel extends ChangeNotifier { final BookAppointmentsRepo bookAppointmentsRepo; final ErrorHandlerService errorHandlerService; final NavigationService navigationService; - HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); + HmgServicesViewModel( + {required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); bool isCmcOrdersLoading = false; bool isCmcServicesLoading = false; @@ -53,12 +54,12 @@ class HmgServicesViewModel extends ChangeNotifier { // HHC order creation state (no hospital selection needed for home healthcare) GetCMCServicesResponseModel? selectedServiceForHhcOrder; - - List relationTypes =[]; - List getAllCitiesList =[]; - List searchReferralList =[]; + List relationTypes = []; + List getAllCitiesList = []; + List searchReferralList = []; Future getOrdersList() async {} + // HHC multiple services selection List selectedHhcServices = []; @@ -332,10 +333,7 @@ class HmgServicesViewModel extends ChangeNotifier { await getAllHhcOrders(); } - Future getAllHhcOrders({ - Function(dynamic)? onSuccess, - Function(String)? onError, - }) async { + Future getAllHhcOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHhcOrdersLoading = true; notifyListeners(); @@ -541,16 +539,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getRelationshipTypes(); result.fold( - (failure) async { - + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { - + (apiResponse) { if (apiResponse.messageStatus == 1) { relationTypes = apiResponse.data ?? []; notifyListeners(); @@ -567,7 +563,6 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future getAllCities({ Function(dynamic)? onSuccess, Function(String)? onError, @@ -576,16 +571,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getAllCities(); result.fold( - (failure) async { - + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { - + (apiResponse) { if (apiResponse.messageStatus == 1) { getAllCitiesList = apiResponse.data ?? []; notifyListeners(); @@ -607,22 +600,20 @@ class HmgServicesViewModel extends ChangeNotifier { Function(GenericApiModel)? onSuccess, Function(String)? onError, }) async { - notifyListeners(); final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { - notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -642,22 +633,20 @@ class HmgServicesViewModel extends ChangeNotifier { Function(GenericApiModel)? onSuccess, Function(String)? onError, }) async { - notifyListeners(); final result = await hmgServicesRepo.checkEReferralActivationCode(requestModel); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { - notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -672,28 +661,25 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future createEReferral({ required CreateEReferralRequestModel requestModel, Function(GenericApiModel)? onSuccess, Function(String)? onError, }) async { - notifyListeners(); final result = await hmgServicesRepo.createEReferral(requestModel); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { - notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -708,29 +694,24 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - - Future searchEReferral({ required SearchEReferralRequestModel requestModel, Function(dynamic)? onSuccess, Function(String)? onError, }) async { - notifyListeners(); final result = await hmgServicesRepo.searchEReferral(requestModel); result.fold( - (failure) async { - + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { - + (apiResponse) { if (apiResponse.messageStatus == 1) { searchReferralList = apiResponse.data ?? []; notifyListeners(); @@ -747,28 +728,26 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future navigateToOTPScreen( - {required OTPTypeEnum otpTypeEnum, - required String phoneNumber, - required String loginToken, - required Function onSuccess, - }) async { - + Future navigateToOTPScreen({ + required OTPTypeEnum otpTypeEnum, + required String phoneNumber, + required String loginToken, + required Function onSuccess, + }) async { navigationService.pushToOtpScreen( phoneNumber: phoneNumber, isFormFamilyFile: false, checkActivationCode: (int activationCode) async { - checkEReferralActivationCode( requestModel: CheckActivationCodeForEReferralRequestModel( logInTokenID: loginToken, activationCode: activationCode.toString(), ), onSuccess: (GenericApiModel response) { - onSuccess(); + onSuccess(); }, onError: (String errorMessage) { - print(errorMessage); + print(errorMessage); }, ); }, @@ -787,5 +766,4 @@ class HmgServicesViewModel extends ChangeNotifier { }, ); } - } diff --git a/lib/features/location/location_view_model.dart b/lib/features/location/location_view_model.dart index c6ea34e..76d7c2c 100644 --- a/lib/features/location/location_view_model.dart +++ b/lib/features/location/location_view_model.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show ChangeNotifier; import 'package:flutter/material.dart'; -import 'package:google_maps_flutter_platform_interface/src/types/camera.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; 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/features/location/GeocodeResponse.dart'; @@ -10,9 +10,6 @@ import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; import 'package:hmg_patient_app_new/features/location/location_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:huawei_map/huawei_map.dart' as HMSCameraServices; -import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; - - import 'PlacePrediction.dart'; @@ -20,12 +17,12 @@ class LocationViewModel extends ChangeNotifier { final LocationRepo locationRepo; final ErrorHandlerService errorHandlerService; - LocationViewModel({required this.locationRepo, required this.errorHandlerService}){ + LocationViewModel({required this.locationRepo, required this.errorHandlerService}) { placeValueInController(); } - List predictions = []; - PlacePrediction? selectedPrediction; + List predictions = []; + PlacePrediction? selectedPrediction; bool isPredictionLoading = false; GeocodeResponse? geocodeResponse; PlaceDetails? placeDetails; @@ -39,12 +36,11 @@ class LocationViewModel extends ChangeNotifier { return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); } - GMSMapServices.CameraPosition getGMSLocation() { return GMSMapServices.CameraPosition(target: GMSMapServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); } - void placeValueInController() async{ + void placeValueInController() async { if (await getIt().isGMSAvailable) { gmsController = Completer(); } else { @@ -54,14 +50,14 @@ class LocationViewModel extends ChangeNotifier { FutureOr getPlacesPrediction(String input) async { predictions = []; - isPredictionLoading= true; + isPredictionLoading = true; final result = await locationRepo.getPlacePredictionsAsInput(input); result.fold( (failure) { errorHandlerService.handleError(failure: failure); }, (apiModel) { - predictions = apiModel.data??[]; + predictions = apiModel.data ?? []; }, ); isPredictionLoading = false; @@ -99,21 +95,20 @@ class LocationViewModel extends ChangeNotifier { handleGMSMapCameraMoved(GMSMapServices.CameraPosition value) { mapCapturedLocation = Location(lat: value.target.latitude, lng: value.target.longitude); - } handleHMSMapCameraMoved(HMSCameraServices.CameraPosition value) { mapCapturedLocation = Location(lat: value.target.lat, lng: value.target.lng); } - handleOnCameraIdle(){ - if(mapCapturedLocation != null) { + handleOnCameraIdle() { + if (mapCapturedLocation != null) { getPlaceEncodedData(mapCapturedLocation!.lat, mapCapturedLocation!.lng); } } void updateSearchQuery(String? value) { - if(value == null || value.isEmpty){ + if (value == null || value.isEmpty) { predictions = []; return; } @@ -123,16 +118,16 @@ class LocationViewModel extends ChangeNotifier { void flushSearchPredictions() { predictions = []; - mapCapturedLocation= null; - placeDetails= null; - geocodeResponse= null; - selectedPrediction= null; + mapCapturedLocation = null; + placeDetails = null; + geocodeResponse = null; + selectedPrediction = null; notifyListeners(); } - FutureOr selectPlacePrediction(PlacePrediction placePrediction) async{ - selectedPrediction= placePrediction; + FutureOr selectPlacePrediction(PlacePrediction placePrediction) async { + selectedPrediction = placePrediction; await getPlaceDetails(placePrediction.placeID); } @@ -141,8 +136,6 @@ class LocationViewModel extends ChangeNotifier { } void moveController(Location location) { - print("moving to location"); - print("gmsController is null or not $gmsController"); if (getIt().isGMSAvailable) { gmsController?.future.then((controller) { controller.animateCamera( @@ -169,4 +162,4 @@ class LocationViewModel extends ChangeNotifier { }); } } -} \ No newline at end of file +} diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 0653495..d077970 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.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/features/my_appointments/models/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; @@ -226,7 +227,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { filteredAppointmentList.addAll(patientAppointmentsHistoryList); if (patientArrivedAppointmentsHistoryList.isNotEmpty) { - getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); + if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) { + // getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); + getPatientAppointmentQueueDetails(); + } } print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); @@ -670,10 +674,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } - Future getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getPatientAppointmentQueueDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { isAppointmentQueueDetailsLoading = true; notifyListeners(); - final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails(appointmentNo: appointmentNo, patientID: patientID); + final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails( + appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); isAppointmentQueueDetailsLoading = false; @@ -692,7 +697,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { isPatientHasQueueAppointment = true; patientQueueDetailsList = apiResponse.data!; for (var element in patientQueueDetailsList) { - if (element.patientID == patientID) { + if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { currentPatientQueueDetails = element; currentQueueStatus = element.callType!; // currentQueueStatus = 2; @@ -701,7 +706,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } // patientQueueDetailsList.first.callType = 1; - patientQueueDetailsList.removeWhere((element) => element.patientID == patientID); + patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); } else { isPatientHasQueueAppointment = false; } diff --git a/lib/main.dart b/lib/main.dart index 714053f..b672e4e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,7 +33,7 @@ import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart index d8acac6..4554873 100644 --- a/lib/presentation/appointments/appointment_queue_page.dart +++ b/lib/presentation/appointments/appointment_queue_page.dart @@ -114,7 +114,7 @@ class AppointmentQueuePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - "IMD W-A-2".needTranslation.toText17(isBold: true), + myAppointmentsVM.patientQueueDetailsList[index].queueNo!.toText17(isBold: true), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index cd1cec8..a35c1ca 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -188,13 +188,14 @@ class AppointmentCard extends StatelessWidget { ? 'Cardiology' : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", ).toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), - isIconPNG: true, - icon: getIt.get().getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - iconSize: 18.h, - labelText: isLoading ? 'Cardiology' : "Patient: ${getIt.get().getAuthenticatedUser()!.firstName!}", - ).toShimmer2(isShow: isLoading), + + // AppCustomChipWidget( + // labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), + // isIconPNG: true, + // icon: getIt.get().getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, + // iconSize: 18.h, + // labelText: isLoading ? 'Cardiology' : "Patient: ${getIt.get().getAuthenticatedUser()!.firstName!}", + // ).toShimmer2(isShow: isLoading), // if (!isFromMedicalReport) // AppCustomChipWidget( // icon: AppAssets.appointment_time_icon, diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index 1fbd22e..5b1d1cd 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -149,7 +149,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { onSuccess: (apiResponse) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { - await myAppointmentsViewModel.getPatientAppointmentQueueDetails(appointmentNo: patientAppointmentHistoryResponseModel.appointmentNo, patientID: patientAppointmentHistoryResponseModel.patientID); + await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 4d24da7..5aa1b7c 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -54,7 +54,7 @@ class _BookAppointmentPageState extends State { void initState() { authVM = context.read(); scheduleMicrotask(() { - bookAppointmentsViewModel.selectedTabIndex = 0; + // bookAppointmentsViewModel.selectedTabIndex = 0; bookAppointmentsViewModel.initBookAppointmentViewModel(); bookAppointmentsViewModel.getLocation(); immediateLiveCareViewModel.initImmediateLiveCare(); @@ -68,6 +68,7 @@ class _BookAppointmentPageState extends State { immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); regionalViewModel = Provider.of(context, listen: true); + getSelectedTabData(bookAppointmentsViewModel.selectedTabIndex); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -85,6 +86,7 @@ class _BookAppointmentPageState extends State { CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + initialIndex: bookAppointmentsVM.selectedTabIndex, tabs: [ CustomTabBarModel(null, "General".needTranslation), CustomTabBarModel(null, "LiveCare".needTranslation), @@ -323,10 +325,12 @@ class _BookAppointmentPageState extends State { ).paddingSymmetrical(24.h, 0.h); case 1: //TODO: Get LiveCare type Select UI from Hussain - return appState.isAuthenticated - ? Column( - children: [ - Container( + return + // appState.isAuthenticated + // ? + Column( + children: [ + Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -445,7 +449,8 @@ class _BookAppointmentPageState extends State { ), ], ).paddingSymmetrical(24.h, 0.h) - : getLiveCareNotLoggedInUI(); + // : getLiveCareNotLoggedInUI() + ; default: SizedBox.shrink(); } diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index d0bd740..d801bdb 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -6,6 +6,7 @@ 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/enums.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'; @@ -27,6 +28,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; @@ -36,8 +38,10 @@ import 'package:provider/provider.dart'; class SelectClinicPage extends StatefulWidget { bool isFromRegionFlow; + HealthCalculatorsTypeEnum? calculatorType; + dynamic calculatedResult; - SelectClinicPage({super.key, this.isFromRegionFlow = false}); + SelectClinicPage({super.key, this.isFromRegionFlow = false, this.calculatorType, this.calculatedResult}); @override State createState() => _SelectClinicPageState(); @@ -69,131 +73,872 @@ class _SelectClinicPageState extends State { bookAppointmentsViewModel = Provider.of(context, listen: false); regionalViewModel = Provider.of(context, listen: true); appState = getIt.get(); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Consumer(builder: (context, bookAppointmentsVM, child) { - return Column( - children: [ - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: LocaleKeys.clinicName.tr(context: context), - controller: searchEditingController, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - focusNode: textFocusNode, - suffix: searchEditingController.text.isNotEmpty - ? GestureDetector( - onTap: () { - searchEditingController.clear(); - bookAppointmentsViewModel.filterClinics(""); - textFocusNode.unfocus(); - }, - child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), - ) - : null, - onChange: (value) { - bookAppointmentsViewModel.filterClinics(value!); - }, - padding: EdgeInsets.symmetric( - vertical: ResponsiveExtension(10).h, - horizontal: ResponsiveExtension(15).h, + if (widget.calculatorType != null) { + return CollapsingListView( + title: "Your ${widget.calculatorType!.name.toCamelCase}", + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), + padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), + child: CustomButton( + text: LocaleKeys.bookAppo.tr(), + onPressed: () { + onClinicSelected(GetClinicsListResponseModel(clinicID: 108, clinicDescription: "asdfds", clinicDescriptionN: "asdfds")); + }, + icon: null, + fontSize: 16.f, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + borderRadius: 12.r, + fontWeight: FontWeight.w500), + ), + child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h), + ); + } else { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.clinicName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + bookAppointmentsViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), ), - ), - bookAppointmentsVM.isLiveCareSchedule - ? ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : 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, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); - }), + bookAppointmentsVM.isLiveCareSchedule + ? ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : 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, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }), + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ) - : ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : 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, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); - }), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ) + : ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : 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, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); + }), + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + }), + ), + ), + ), + ); + } + } + + Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { + switch (widget.calculatorType!) { + case HealthCalculatorsTypeEnum.bmi: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.calories: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bmr: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.idealBodyWeight: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bodyFat: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.crabsProteinFat: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.ovulation: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.deliveryDueDate: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bloodSugar: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bloodCholesterol: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.triglycerides: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + } + } + + Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { + print(calculatedResult); + return Column( + children: [ + if (type == HealthCalculatorsTypeEnum.bmi) ...[ + if (calculatedResult["bmiCategory"] == "Normal") ...[ + calorieWidget( + title: calculatedResult["bmiCategory"], + calories: calculatedResult["bmiResult"], + description: + 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.', + color: Colors.green, + icon: Icons.scale, + hide: true, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[ + calorieWidget( + title: calculatedResult["bmiCategory"], + calories: calculatedResult["bmiResult"], + description: + 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.', + color: Colors.orange, + hide: true, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[ + calorieWidget( + title: calculatedResult["bmiCategory"], + calories: calculatedResult["bmiResult"], + description: + 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.', + color: Colors.red, + hide: true, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.calories) ...[ + if (calculatedResult["calorieRange"] == "Normal") ...[ + calorieWidget( + title: calculatedResult["calorieRange"], + calories: calculatedResult["calories"], + description: + 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.', + color: Colors.green, + icon: Icons.scale, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["calorieRange"] == "Low") ...[ + calorieWidget( + title: calculatedResult["calorieRange"], + calories: calculatedResult["calories"], + description: + 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.', + color: Colors.orange, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["calorieRange"] == "High") ...[ + calorieWidget( + title: calculatedResult["calorieRange"], + calories: calculatedResult["calories"], + description: + 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.', + color: Colors.red, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ + if (calculatedResult["bmrRange"] == "Normal") ...[ + calorieWidget( + title: calculatedResult["bmrRange"], + calories: calculatedResult["bmr"], + description: + 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.', + color: Colors.green, + hide: true, + icon: Icons.scale, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["bmrRange"] == "Low") ...[ + calorieWidget( + title: calculatedResult["bmrRange"], + calories: calculatedResult["bmr"], + description: + 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.', + color: Colors.orange, + hide: true, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["bmrRange"] == "High") ...[ + calorieWidget( + title: calculatedResult["bmrRange"], + calories: calculatedResult["bmr"], + description: + 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.', + color: Colors.red, + hide: true, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[ + if (calculatedResult["status"] == "Normal") ...[ + calorieWidget( + title: calculatedResult["status"], + calories: calculatedResult["ibw"], + hide: true, + description: (() { + final diff = calculatedResult["difference"]; + if (diff is num) { + if (diff == 0) { + return 'Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.'; + } else if (diff > 0) { + return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.'; + } else { + return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.'; + } + } + return 'Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.'; + })(), + color: Colors.green, + icon: Icons.scale, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["status"] == "Underweight") ...[ + calorieWidget( + title: calculatedResult["status"], + calories: calculatedResult["ibw"], + hide: true, + description: (() { + final diff = calculatedResult["difference"]; + if (diff is num) { + return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.'; + } + return 'You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.'; + })(), + color: Colors.orange, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["status"] == "Overweight") ...[ + calorieWidget( + title: calculatedResult["status"], + calories: calculatedResult["ibw"], + hide: true, + description: (() { + final diff = calculatedResult["difference"]; + if (diff is num) { + return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.'; + } + return 'You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.'; + })(), + color: Colors.red, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[ + if (calculatedResult["dietType"] == "Very Low Crabs") ...[ + calorieWidget( + title: calculatedResult["dietType"].toString(), + calories: calculatedResult["totalCalories"], + description: (() { + final total = calculatedResult["totalCalories"]; + final carbsG = calculatedResult["carbsGrams"]; + final carbsC = calculatedResult["carbsCalories"]; + final protG = calculatedResult["proteinGrams"]; + final protC = calculatedResult["proteinCalories"]; + final fatG = calculatedResult["fatGrams"]; + final fatC = calculatedResult["fatCalories"]; + final base = + 'Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.'; + // if (total is num) { + // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; + // } + return base; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.orange, + hide: true, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["dietType"] == "Low Crabs") ...[ + calorieWidget( + title: calculatedResult["dietType"].toString(), + calories: calculatedResult["totalCalories"], + description: (() { + final total = calculatedResult["totalCalories"]; + final carbsG = calculatedResult["carbsGrams"]; + final carbsC = calculatedResult["carbsCalories"]; + final protG = calculatedResult["proteinGrams"]; + final protC = calculatedResult["proteinCalories"]; + final fatG = calculatedResult["fatGrams"]; + final fatC = calculatedResult["fatCalories"]; + final base = + 'Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.'; + // if (total is num) { + // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; + // } + return base; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.orange, + hide: true, + icon: Icons.trending_down, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[ + calorieWidget( + title: calculatedResult["dietType"].toString(), + calories: calculatedResult["totalCalories"], + description: (() { + final total = calculatedResult["totalCalories"]; + final carbsG = calculatedResult["carbsGrams"]; + final carbsC = calculatedResult["carbsCalories"]; + final protG = calculatedResult["proteinGrams"]; + final protC = calculatedResult["proteinCalories"]; + final fatG = calculatedResult["fatGrams"]; + final fatC = calculatedResult["fatCalories"]; + final base = + 'Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.'; + // if (total is num) { + // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; + // } + return base; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.green, + hide: true, + icon: Icons.scale, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[ + calorieWidget( + title: calculatedResult["dietType"].toString(), + calories: calculatedResult["totalCalories"], + description: (() { + final total = calculatedResult["totalCalories"]; + final carbsG = calculatedResult["carbsGrams"]; + final carbsC = calculatedResult["carbsCalories"]; + final protG = calculatedResult["proteinGrams"]; + final protC = calculatedResult["proteinCalories"]; + final fatG = calculatedResult["fatGrams"]; + final fatC = calculatedResult["fatCalories"]; + final base = + 'USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.'; + // if (total is num) { + // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; + // } + + return base; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.green, + hide: true, + icon: Icons.scale, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["dietType"] == "Zone Diet") ...[ + calorieWidget( + title: calculatedResult["dietType"].toString(), + calories: calculatedResult["totalCalories"], + description: (() { + final total = calculatedResult["totalCalories"]; + final carbsG = calculatedResult["carbsGrams"]; + final carbsC = calculatedResult["carbsCalories"]; + final protG = calculatedResult["proteinGrams"]; + final protC = calculatedResult["proteinCalories"]; + final fatG = calculatedResult["fatGrams"]; + final fatC = calculatedResult["fatCalories"]; + final base = + 'Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.'; + // if (total is num) { + // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; + // } + return base; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.orange, + hide: true, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.ovulation) ...[ + Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.nextPeriodAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], ), - ], - ); - }), + ], + ), + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["lastPeriodDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + SizedBox(height: 16.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["ovulationDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + SizedBox(height: 16.h), + "Fertility Window".toText16(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range." + .toText12(height: 1.5, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + ], + ), + ), + SizedBox( + height: 16.h, + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bulb), + SizedBox( + width: 8.w, + ), + Row( + children: [ + "Tips to keep in mind".toText16(color: AppColors.textColor, weight: FontWeight.w600), + ], + ), + ], + ), + SizedBox(height: 8.h), + Column( + children: [ + _buildTipRow("Drink before you feel thirsty."), + SizedBox(height: 4.h), + _buildTipRow("Keep a refillable bottle next to you."), + SizedBox(height: 4.h), + _buildTipRow("Track your daily intake to stay motivated."), + SizedBox(height: 4.h), + _buildTipRow("Choose sparkling water instead of soda."), + ], + ).paddingOnly(left: 4.w, right: 4.w) + ], + ), + ), + ], + ) + ] else if (type == HealthCalculatorsTypeEnum.deliveryDueDate) ...[ + Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDueDateAccordion, width: 30.h, height: 30.h), + SizedBox(width: 8.w), + calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + Expanded(child: SizedBox(width: 8.w)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["dueDateDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "First Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["firstTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Second Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["secondTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Third Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["thirdTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDayAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + + ], + ) + ], + ], + ); + } + + Widget _buildTipRow(String text) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 6.h, right: 8.w), + child: CircleAvatar( + radius: 3.r, + backgroundColor: AppColors.textColorLight, ), ), + Expanded( + child: text.toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w500, height: 1.2.h), + ), + ], + ); + } + + Widget calorieWidget({required String title, required dynamic calories, required String description, required Color color, required IconData icon, bool hide = false, List? chipsItems}) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(title), isBold: true), + SizedBox(width: 10.w), + hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600) + ], + ), + _buildStatusIcon(title) + ], + ), + SizedBox(height: 16.h), + if (chipsItems != null && chipsItems.isNotEmpty) ...[ + Wrap( + spacing: 8.0, // Horizontal space between chips + runSpacing: 4.0, // Vertical space between rows of chips + children: chipsItems.map((item) { + return CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: item, + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor) + .paddingOnly(bottom: 4.h); + }).toList(), + ), + SizedBox(height: 8.h), + ], + title.toText16(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox(height: 8.h), + description.toText12(height: 1.2.h, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + ], ), ); } + Widget _buildStatusIcon(String title) { + final key = title.trim(); + if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); + } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { + return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); + } else { + return SizedBox(); + } + } + + Color getTextColor(String title) { + final key = title.trim(); + if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + return AppColors.successColor; + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + return AppColors.warningColor; + } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { + return AppColors.errorColor; + } else { + return Colors.black; + } + } + void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) { bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic); bookAppointmentsViewModel.setIsDoctorsListLoading(true); @@ -210,9 +955,12 @@ class _SelectClinicPageState extends State { if (clinic.isLiveCareClinicAndOnline ?? false) { Navigator.of(context).push( CustomPageRoute( - page: SelectLivecareClinicPage(onNegativeClicked: (){ + page: SelectLivecareClinicPage( + onNegativeClicked: () { handleDoctorScreen(clinic); - },), + }, + selectedClinic: clinic, + ), ), ); } else { @@ -226,9 +974,7 @@ class _SelectClinicPageState extends State { //Dental Clinic Flow if (clinic.clinicID == 17) { if (appState.isAuthenticated) { - initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel - .currentlySelectedHospitalFromRegionFlow ?? - "0")); + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); return; } else { bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); @@ -239,8 +985,7 @@ class _SelectClinicPageState extends State { ); return; } - } - else if (clinic.clinicID == 253) { + } else if (clinic.clinicID == 253) { bookAppointmentsViewModel.resetLaserData(); bookAppointmentsViewModel.getLaserClinic(); Navigator.push( @@ -267,8 +1012,7 @@ class _SelectClinicPageState extends State { } } - void openRegionListBottomSheet( - BuildContext context, RegionBottomSheetType type) { + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { bookAppointmentsViewModel.setProjectID(null); regionalViewModel.flush(); @@ -276,12 +1020,11 @@ class _SelectClinicPageState extends State { // AppointmentViaRegionViewmodel? viewmodel = null; showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (_, data, __) { - return getRegionalSelectionWidget(data); - }), callBackFunc: () { - }); + return getRegionalSelectionWidget(data); + }), callBackFunc: () {}); } - Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) { return RegionBottomSheetBody(); } @@ -306,7 +1049,7 @@ class _SelectClinicPageState extends State { if (appState.isAuthenticated) { initDentalAppointment(); return SizedBox.shrink(); - }else { + } else { bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); } } @@ -319,8 +1062,7 @@ class _SelectClinicPageState extends State { } bookAppointmentsViewModel.setProjectID(id); return SizedBox.shrink(); - } - else { + } else { return SizedBox.shrink(); } return SizedBox.shrink(); @@ -474,9 +1216,7 @@ class _SelectClinicPageState extends State { void initDentalAppointment() async { await Future.delayed(Duration(milliseconds: 300)); - initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel - .currentlySelectedHospitalFromRegionFlow ?? - "0")); + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); return; } } diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 719452d..2362617 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -1,22 +1,37 @@ 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/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'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +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 SelectLivecareClinicPage extends StatelessWidget { final VoidCallback? onNegativeClicked; - const SelectLivecareClinicPage({super.key, this.onNegativeClicked}); + late GetClinicsListResponseModel selectedClinic; + + SelectLivecareClinicPage({super.key, this.onNegativeClicked, required this.selectedClinic}); + + late ImmediateLiveCareViewModel immediateLiveCareViewModel; @override Widget build(BuildContext context) { + immediateLiveCareViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -103,7 +118,36 @@ class SelectLivecareClinicPage extends StatelessWidget { children: [ CustomButton( text: "Yes please, I am in a hurry".needTranslation, - onPressed: () {}, + onPressed: () async { + Navigator.pop(context); + GetLiveCareClinicListResponseModel liveCareClinic = GetLiveCareClinicListResponseModel( + iD: selectedClinic.liveCareClinicID, + serviceID: selectedClinic.liveCareServiceID, + serviceName: selectedClinic.clinicDescription, + serviceNameN: selectedClinic.clinicDescriptionN, + ); + + immediateLiveCareViewModel.setLiveCareSelectedCallType(1); + immediateLiveCareViewModel.setImmediateLiveCareSelectedClinic(liveCareClinic); + LoaderBottomSheet.showLoader(loadingText: "Fetching fees, Please wait...".needTranslation); + await immediateLiveCareViewModel.getLiveCareImmediateAppointmentFees(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute( + page: ImmediateLiveCarePaymentDetails(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 3be209b..18b656c 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -170,8 +170,6 @@ class _CmcSelectionReviewPageState extends State { latitude: double.parse(lat), longitude: double.parse(lng), address: hospitalName, - title: "Hospital Location".needTranslation, - showTitle: false, showAddress: false, padding: EdgeInsets.zero, onDirectionsTap: () => _launchDirections(selectedHospital), diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart index 5529b93..8b9ad89 100644 --- a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -366,7 +366,12 @@ class _ComprehensiveCheckupPageState extends State { if (pendingOrder == null && _selectedServiceId != null) { return SafeArea( top: false, - child: Padding( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), child: CustomButton( borderWidth: 0, diff --git a/lib/presentation/health_calculators/health_calculator_result.dart b/lib/presentation/health_calculators/health_calculator_result.dart index 5abe410..e69de29 100644 --- a/lib/presentation/health_calculators/health_calculator_result.dart +++ b/lib/presentation/health_calculators/health_calculator_result.dart @@ -1,444 +0,0 @@ -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/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; - -class HealthCalculatorResultPage extends StatelessWidget { - HealthCalculatorsTypeEnum calculatorType; - dynamic calculatedResult; - - HealthCalculatorResultPage({super.key, required this.calculatorType, this.calculatedResult}); - - @override - Widget build(BuildContext context) { - return CollapsingListView( - title: "Your ${calculatorType.name.toCamelCase}", - bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), - padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), - child: CustomButton( - text: LocaleKeys.bookAppo.tr(), - onPressed: () {}, - icon: null, - fontSize: 16.f, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - borderRadius: 12.r, - fontWeight: FontWeight.w500), - ), - child: getCalculatorResultWidget(type: calculatorType, calculatedResult: calculatedResult).paddingSymmetrical(18.w, 24.h), - ); - } - - Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { - switch (calculatorType) { - case HealthCalculatorsTypeEnum.bmi: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.calories: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.bmr: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.idealBodyWeight: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.bodyFat: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.crabsProteinFat: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.ovulation: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - case HealthCalculatorsTypeEnum.deliveryDueDate: - return resultWidget(type: calculatorType, calculatedResult: calculatedResult); - } - } - - Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { - print(calculatedResult); - return Column( - children: [ - if (type == HealthCalculatorsTypeEnum.bmi) ...[ - if (calculatedResult["bmiCategory"] == "Normal") ...[ - CalorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], - description: - 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.', - color: Colors.green, - icon: Icons.scale, - hide: true, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[ - CalorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], - description: - 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.', - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[ - CalorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], - description: - 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.', - color: Colors.red, - hide: true, - icon: Icons.warning_rounded, - ), - ] - ] else if (type == HealthCalculatorsTypeEnum.calories) ...[ - if (calculatedResult["calorieRange"] == "Normal") ...[ - CalorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], - description: - 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.', - color: Colors.green, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["calorieRange"] == "Low") ...[ - CalorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], - description: - 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.', - color: Colors.orange, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["calorieRange"] == "High") ...[ - CalorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], - description: - 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.', - color: Colors.red, - icon: Icons.warning_rounded, - ), - ] - ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ - if (calculatedResult["bmrRange"] == "Normal") ...[ - CalorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], - description: - 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.', - color: Colors.green, - hide: true, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["bmrRange"] == "Low") ...[ - CalorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], - description: - 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.', - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["bmrRange"] == "High") ...[ - CalorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], - description: - 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.', - color: Colors.red, - hide: true, - icon: Icons.warning_rounded, - ), - ] - ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[ - if (calculatedResult["status"] == "Normal") ...[ - CalorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], - hide: true, - description: (() { - final diff = calculatedResult["difference"]; - if (diff is num) { - if (diff == 0) { - return 'Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.'; - } else if (diff > 0) { - return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.'; - } else { - return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.'; - } - } - return 'Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.'; - })(), - color: Colors.green, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["status"] == "Underweight") ...[ - CalorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], - hide: true, - description: (() { - final diff = calculatedResult["difference"]; - if (diff is num) { - return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.'; - } - return 'You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.'; - })(), - color: Colors.orange, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["status"] == "Overweight") ...[ - CalorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], - hide: true, - description: (() { - final diff = calculatedResult["difference"]; - if (diff is num) { - return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.'; - } - return 'You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.'; - })(), - color: Colors.red, - icon: Icons.warning_rounded, - ), - ] - ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[ - if (calculatedResult["dietType"] == "Very Low Crabs") ...[ - CalorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - 'Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.'; - if (total is num) { - return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - } - return base; - })(), - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Low Crabs") ...[ - CalorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - 'Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.'; - if (total is num) { - return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - } - return base; - })(), - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[ - CalorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - 'Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.'; - if (total is num) { - return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - } - return base; - })(), - color: Colors.green, - hide: true, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[ - CalorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - 'USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.'; - if (total is num) { - return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - } - return base; - })(), - color: Colors.green, - hide: true, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Zone Diet") ...[ - CalorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - 'Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.'; - if (total is num) { - return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - } - return base; - })(), - color: Colors.orange, - hide: true, - icon: Icons.warning_rounded, - ), - ] - ] - ], - ); - } -} - -class CalorieWidget extends StatelessWidget { - final String title; - final dynamic calories; - final String description; - final Color color; - final IconData icon; - final bool hide; - - const CalorieWidget({super.key, required this.title, required this.calories, required this.description, required this.color, required this.icon, this.hide = false}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(), isBold: true), - SizedBox(width: 10.w), - hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600) - ], - ), - _buildStatusIcon() - ], - ), - SizedBox(height: 16.h), - title.toText16(color: AppColors.textColor, weight: FontWeight.w600), - SizedBox(height: 8.h), - Text( - description, - style: TextStyle( - fontSize: 14, - color: Colors.grey[700], - height: 1.5, - ), - textAlign: TextAlign.start, - ), - ], - ), - ); - } - // - // Widget _buildStatusIcon() { - // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') { - // return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); - // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') { - // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); - // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") { - // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); - // } else { - // return SizedBox(); - // } - // } - // - // Color getTextColor() { - // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') { - // return AppColors.successColor; - // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') { - // return AppColors.warningColor; - // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") { - // return AppColors.errorColor; - // } else { - // return Colors.black; - // } - // } - - Widget _buildStatusIcon() { - final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { - return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { - return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); - } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { - return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); - } else { - return SizedBox(); - } - } - - Color getTextColor() { - final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { - return AppColors.successColor; - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { - return AppColors.warningColor; - } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { - return AppColors.errorColor; - } else { - return Colors.black; - } - } -} diff --git a/lib/presentation/health_calculators/health_calculators_page.dart b/lib/presentation/health_calculators/health_calculators_page.dart deleted file mode 100644 index 8ac7a22..0000000 --- a/lib/presentation/health_calculators/health_calculators_page.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'dart:async'; - -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/enums.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/presentation/health_calculators/health_calculator_detailed_page.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/health_card.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.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/routes/custom_page_route.dart'; - -class HealthCalculatorsPage extends StatefulWidget { - const HealthCalculatorsPage({super.key}); - - @override - State createState() => _HealthCalculatorsPageState(); -} - -class _HealthCalculatorsPageState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - DialogService dialogService = getIt.get(); - return CollapsingListView( - title: "Health Calculators".needTranslation, - child: Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none), - SizedBox(width: 12.w), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - "General Health".needTranslation.toText16(weight: FontWeight.w600), - "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) - ], - ), - ), - SizedBox( - width: 12.w, - ), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ], - ).paddingAll(16.w)) - .onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Calculator".needTranslation, - message: "", - child: showCalculatorsItems(type: HealthCalculatorEnum.general), - onOkPressed: () {}, - ); - }), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none), - SizedBox(width: 12.w), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - "Women's Health".needTranslation.toText16(weight: FontWeight.w600), - "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) - ], - ), - ), - SizedBox(width: 12.w), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ], - ).paddingAll(16.w)) - .onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: "Select Calculator".needTranslation, - message: "", - child: showCalculatorsItems(type: HealthCalculatorEnum.women), - onOkPressed: () {}, - ); - }), - ], - ).paddingSymmetrical(20.w, 24.h)); - } - - Widget showCalculatorsItems({required HealthCalculatorEnum type}) { - return GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, // 4 icons per row - crossAxisSpacing: 16.w, - mainAxisSpacing: 16.w, - childAspectRatio: 0.80), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return HealthCard( - icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, - labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, - onTap: () { - Navigator.of(context).push( - CustomPageRoute( - page: HealthCalculatorDetailedPage( - calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, - ), - ), - ); - }, - ); - }, - ); - } - - final List generalHealthServices = [ - HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi), - HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories), - HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr), - HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight), - HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat), - HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat), - ]; - - final List womenHealthServices = [ - HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation), - HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate), - ]; -} - -class HealthComponentModel { - String title; - String? subTitle; - String icon; - Color? iconColor; - Color? bgColor; - Color? textColor; - HealthCalculatorsTypeEnum type; - - HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type}); -} diff --git a/lib/presentation/health_calculators/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart similarity index 61% rename from lib/presentation/health_calculators/health_calculator_detailed_page.dart rename to lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index ae7d46b..e0f3a7a 100644 --- a/lib/presentation/health_calculators/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -1,25 +1,28 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_sugar.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmi.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmr.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/calories.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/crabs.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/dduedate.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_result.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bf.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmi.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmr.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/calories.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/crabs.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/dduedate.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ibw.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ovulation.dart'; import 'package:hmg_patient_app_new/services/dialog_service.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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; class HealthCalculatorDetailedPage extends StatefulWidget { HealthCalculatorsTypeEnum calculatorType; @@ -40,24 +43,34 @@ class _HealthCalculatorDetailedPageState extends State(builder: (context, provider, _) { return CollapsingListView( title: widget.calculatorType.displayName, - bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), - padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), - child: CustomButton( - text: "Calculate".needTranslation, - onPressed: () { - if (calculatedResult == null) return; - DialogService dialogService = getIt.get(); - Navigator.of(context).push(CustomPageRoute( - page: HealthCalculatorResultPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult), - )); - }, - icon: null, - fontSize: 16.f, - backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor, - borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor, - borderRadius: 12.r, - fontWeight: FontWeight.w500)), + bottomChild: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar || + widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol || + widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides + ? SizedBox() + : Container( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), + padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), + child: CustomButton( + text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar || + widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol || + widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides + ? "Convert".needTranslation + : "Calculate".needTranslation, + onPressed: () { + if (calculatedResult == null) return; + + DialogService dialogService = getIt.get(); + Navigator.of(context).push(CustomPageRoute( + page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult), + )); + }, + icon: null, + fontSize: 16.f, + backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor, + borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor, + borderRadius: 12.r, + fontWeight: FontWeight.w500)), child: getCalculatorWidget( type: widget.calculatorType, onCalculate: (result) { @@ -85,6 +98,12 @@ class _HealthCalculatorDetailedPageState extends State? deliveryResult; + Map? bloodSugarResult; + Map? bloodCholesterolResult; + Map? triglyceridesResult; + + String? _mgdlValue; + + String? get mgdlValue => _mgdlValue; + + String? _mmolValue; + + String? get mmolValue => _mmolValue; + + String _activeUnit = 'mg/dL'; // current source unit + String get activeUnit => _activeUnit; + + + // ================== BLOOD CHOLESTEROL ================== + String? _cholMgdlValue; + String? _cholMmolValue; + String _cholActiveUnit = 'mg/dL'; + + String? get cholMgdlValue => _cholMgdlValue; + String? get cholMmolValue => _cholMmolValue; + String get cholActiveUnit => _cholActiveUnit; + + // ================== TRIGLYCERIDES ================== + String? _triMgdlValue; + String? _triMmolValue; + String _triActiveUnit = 'mg/dL'; + + String? get triMgdlValue => _triMgdlValue; + String? get triMmolValue => _triMmolValue; + String get triActiveUnit => _triActiveUnit; + + // Generic helpers void calculateBMI({required String heightText, required String weightText, required String heightUnit, required String weightUnit}) { @@ -113,7 +148,15 @@ class HealthCalcualtorViewModel extends ChangeNotifier { } // BMR / Calories (shared logic) - void calculateBMRAndCalories({required String heightText, required String weightText, required String ageText, required String heightUnit, required String weightUnit, required String gender, required String activityLevel, bool forCaloriesOnly = false}) { + void calculateBMRAndCalories( + {required String heightText, + required String weightText, + required String ageText, + required String heightUnit, + required String weightUnit, + required String gender, + required String activityLevel, + bool forCaloriesOnly = false}) { if (heightText.trim().isEmpty || weightText.trim().isEmpty || ageText.trim().isEmpty) { bmrResult = null; caloriesResult = null; @@ -303,7 +346,16 @@ class HealthCalcualtorViewModel extends ChangeNotifier { } // Body Fat - void calculateBodyFat({required String heightText, required String neckText, required String waistText, required String hipText, required String heightUnit, required String neckUnit, required String waistUnit, required String hipUnit, required String gender}) { + void calculateBodyFat( + {required String heightText, + required String neckText, + required String waistText, + required String hipText, + required String heightUnit, + required String neckUnit, + required String waistUnit, + required String hipUnit, + required String gender}) { if (heightText.trim().isEmpty || neckText.trim().isEmpty || waistText.trim().isEmpty) { bodyFatResult = null; bodyFatCategory = null; @@ -548,6 +600,29 @@ class HealthCalcualtorViewModel extends ChangeNotifier { return months[month - 1]; } + String _getDayName(int weekday) { + // DateTime.weekday returns 1 for Monday and 7 for Sunday. + const List days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + return days[weekday - 1]; + } + + String _formatDateWithDayName(DateTime? date) { + if (date == null) return ''; + String dayName = _getDayName(date.weekday); // Gets 'Monday', 'Tuesday', etc. + return dayName; + } + + Map convertBloodSugar(double value, String fromUnit) { + if (fromUnit == 'mg/dL') { + final mmolL = value / 18.0182; + return {'mgdL': value, 'mmolL': mmolL}; + } else if (fromUnit == 'mmol/L') { + final mgdL = value * 18.0182; + return {'mgdL': mgdL, 'mmolL': value}; + } + return {'mgdL': 0, 'mmolL': 0}; + } + String _determineCyclePhase(int daysSincePeriod, int follicularPhase, int cycleLength) { int normalizedDay = daysSincePeriod % cycleLength; @@ -600,6 +675,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier { String cyclePhase = _determineCyclePhase(daysSincePeriod, follicularPhase, cycleLength); ovulationResult = { + 'lastPeriodDay': _formatDateWithDayName(parsedDate), + 'ovulationDay': _formatDateWithDayName(ovulationDate), 'lastPeriodDate': _formatDate(parsedDate), 'ovulationDate': _formatDate(ovulationDate), 'fertileWindowStart': _formatDate(fertileStart), @@ -629,7 +706,7 @@ class HealthCalcualtorViewModel extends ChangeNotifier { DateTime? lmp; try { lmp = DateTime.tryParse(isoDateString) ?? _tryParseDate(isoDateString); - } catch (e) { + } catch (_) { lmp = null; } @@ -639,19 +716,293 @@ class HealthCalcualtorViewModel extends ChangeNotifier { return; } - DateTime dueDate = lmp.add(Duration(days: 280)); - String dueDateStr = _formatDate(dueDate); - String lmpStr = _formatDate(lmp); + // Pregnancy constants + const int totalPregnancyDays = 280; + + // Calculate main dates + final DateTime dueDate = lmp.add(const Duration(days: totalPregnancyDays)); + + // Trimester calculations + final DateTime firstTrimesterStart = lmp; + final DateTime firstTrimesterEnd = lmp.add(const Duration(days: 83)); + + final DateTime secondTrimesterStart = + firstTrimesterEnd.add(const Duration(days: 1)); + final DateTime secondTrimesterEnd = + lmp.add(const Duration(days: 195)); + + final DateTime thirdTrimesterStart = + secondTrimesterEnd.add(const Duration(days: 1)); + final DateTime thirdTrimesterEnd = dueDate; + + deliveryResult = { + 'lmpDate': _formatDate(lmp), + 'dueDate': _formatDate(dueDate), + // Raw DateTime (useful for logic/UI) + 'lmpDateTime': lmp, + 'dueDateTime': dueDate, + 'dueDateDay' : _formatDateWithDayName(dueDate), + // Trimester info + 'firstTrimester': { + 'start': _formatDate(firstTrimesterStart), + 'end': _formatDate(firstTrimesterEnd), + 'weeks': '1–12', + 'startDateTime': firstTrimesterStart, + 'endDateTime': firstTrimesterEnd, + }, + 'secondTrimester': { + 'start': _formatDate(secondTrimesterStart), + 'end': _formatDate(secondTrimesterEnd), + 'weeks': '13–27', + 'startDateTime': secondTrimesterStart, + 'endDateTime': secondTrimesterEnd, + }, + 'thirdTrimester': { + 'start': _formatDate(thirdTrimesterStart), + 'end': _formatDate(thirdTrimesterEnd), + 'weeks': '28–40', + 'startDateTime': thirdTrimesterStart, + 'endDateTime': thirdTrimesterEnd, + }, + }; - deliveryResult = {'lmpDate': lmpStr, 'dueDate': dueDateStr, 'lmpDateTime': lmp, 'dueDateTime': dueDate}; notifyListeners(); } + + // Blood sugar conversions + void calculateBloodSugar({required String valueText, required String unit}) { + if (valueText.trim().isEmpty) { + bloodSugarResult = null; + notifyListeners(); + return; + } + + try { + double value = double.parse(valueText.trim()); + + if (value <= 0) { + bloodSugarResult = null; + notifyListeners(); + return; + } + + double converted; + String convertedUnit; + + if (unit == 'mg/dL' || unit == 'MG/DL' || unit.toLowerCase().contains('mg')) { + // mg/dL -> mmol/L (glucose): divide by 18.0182 + converted = value / 18.0182; + convertedUnit = 'mmol/L'; + } else { + // mmol/L -> mg/dL + converted = value * 18.0182; + convertedUnit = 'mg/dL'; + } + + // round to sensible precision + double convertedRounded = double.parse(converted.toStringAsFixed(2)); + + bloodSugarResult = { + 'input': value, + 'inputUnit': unit, + 'converted': convertedRounded, + 'convertedUnit': convertedUnit, + }; + notifyListeners(); + } catch (e) { + bloodSugarResult = null; + notifyListeners(); + } + } + // expose map-like results for widgets to forward to parent Map? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory}; + Map? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange}; + Map? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus}; + Map? get bodyFatResultMap => bodyFatResult == null ? null : {'fatPercentage': bodyFatResult, 'fatCategory': bodyFatCategory}; - Map? get macrosResultMap => carbsCalories == null ? null : {'totalCalories': carbsCalories! + proteinCalories! + fatCalories!, 'carbsGrams': carbsGrams, 'carbsCalories': carbsCalories, 'proteinGrams': proteinGrams, 'proteinCalories': proteinCalories, 'fatGrams': fatGrams, 'fatCalories': fatCalories, 'dietType': dietType}; -} + Map? get macrosResultMap => carbsCalories == null + ? null + : { + 'totalCalories': carbsCalories! + proteinCalories! + fatCalories!, + 'carbsGrams': carbsGrams, + 'carbsCalories': carbsCalories, + 'proteinGrams': proteinGrams, + 'proteinCalories': proteinCalories, + 'fatGrams': fatGrams, + 'fatCalories': fatCalories, + 'dietType': dietType + }; + + + + void onBloodSugarChanged(String value, String fromUnit) { + _activeUnit = fromUnit; + + if (value.isEmpty) { + _mgdlValue = null; + _mmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _mgdlValue = value; + _mmolValue = (parsed / 18.0182).toStringAsFixed(1); + } else { + _mmolValue = value; + _mgdlValue = (parsed * 18.0182).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchBloodSugarValues() { + if (_activeUnit == 'mg/dL') { + final mmol = double.tryParse(_mmolValue ?? ''); + if (mmol == null) return; + + _activeUnit = 'mmol/L'; + + // mmol stays as source + _mmolValue = mmol.toStringAsFixed(1); + _mgdlValue = (mmol * 18.0182).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_mgdlValue ?? ''); + if (mgdl == null) return; + + _activeUnit = 'mg/dL'; + + // mg/dL stays as source + _mgdlValue = mgdl.toStringAsFixed(0); + _mmolValue = (mgdl / 18.0182).toStringAsFixed(1); + } + + notifyListeners(); + } + // --- NEW: Method to clear the values --- + void clearBloodSugar() { + _mgdlValue = null; + _mmolValue = null; + notifyListeners(); + } + + + + void onBloodCholesterolChanged(String value, String fromUnit) { + _cholActiveUnit = fromUnit; + + if (value.isEmpty) { + _cholMgdlValue = null; + _cholMmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _cholMgdlValue = value; + _cholMmolValue = (parsed / 38.67).toStringAsFixed(2); + } else { + _cholMmolValue = value; + _cholMgdlValue = (parsed * 38.67).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchBloodCholesterolValues() { + if (_cholActiveUnit == 'mg/dL') { + final mmol = double.tryParse(_cholMmolValue ?? ''); + if (mmol == null) return; + + _cholActiveUnit = 'mmol/L'; + + _cholMmolValue = mmol.toStringAsFixed(2); + _cholMgdlValue = (mmol * 38.67).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_cholMgdlValue ?? ''); + if (mgdl == null) return; + + _cholActiveUnit = 'mg/dL'; + + _cholMgdlValue = mgdl.toStringAsFixed(0); + _cholMmolValue = (mgdl / 38.67).toStringAsFixed(2); + } + + notifyListeners(); + } + + + void clearBloodCholesterol() { + _cholMgdlValue = null; + _cholMmolValue = null; + _cholActiveUnit = 'mg/dL'; + notifyListeners(); + } + + + + void onTriglyceridesChanged(String value, String fromUnit) { + _triActiveUnit = fromUnit; + + if (value.isEmpty) { + _triMgdlValue = null; + _triMmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _triMgdlValue = value; + _triMmolValue = (parsed / 88.57).toStringAsFixed(2); + } else { + _triMmolValue = value; + _triMgdlValue = (parsed * 88.57).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchTriglyceridesValues() { + if (_triActiveUnit == 'mg/dL') { + final mmol = double.tryParse(_triMmolValue ?? ''); + if (mmol == null) return; + + _triActiveUnit = 'mmol/L'; + _triMmolValue = mmol.toStringAsFixed(2); + _triMgdlValue = (mmol * 88.57).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_triMgdlValue ?? ''); + if (mgdl == null) return; + + _triActiveUnit = 'mg/dL'; + _triMgdlValue = mgdl.toStringAsFixed(0); + _triMmolValue = (mgdl / 88.57).toStringAsFixed(2); + } + + notifyListeners(); + } + + void clearTriglycerides() { + _triMgdlValue = null; + _triMmolValue = null; + _triActiveUnit = 'mg/dL'; + notifyListeners(); + } + + + +} diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart new file mode 100644 index 0000000..702e04c --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/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/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/health_card.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.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/routes/custom_page_route.dart'; + +class HealthCalculatorsPage extends StatefulWidget { + HealthCalConEnum type; + + HealthCalculatorsPage({super.key, required this.type}); + + @override + State createState() => _HealthCalculatorsPageState(); +} + +class _HealthCalculatorsPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + DialogService dialogService = getIt.get(); + return CollapsingListView( + title: widget.type == HealthCalConEnum.calculator ? "Health Calculators".needTranslation : "Health Converters".needTranslation, + child: widget.type == HealthCalConEnum.calculator + ? Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "General Health".needTranslation.toText16(weight: FontWeight.w600), + "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + ], + ), + ), + SizedBox( + width: 12.w, + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).paddingAll(16.w)) + .onPress(() { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: "Select Calculator".needTranslation, + message: "", + child: showCalculatorsItems(type: HealthCalculatorEnum.general), + onOkPressed: () {}, + ); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Women's Health".needTranslation.toText16(weight: FontWeight.w600), + "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + ], + ), + ), + SizedBox(width: 12.w), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).paddingAll(16.w)) + .onPress(() { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: "Select Calculator".needTranslation, + message: "", + child: showCalculatorsItems(type: HealthCalculatorEnum.women), + onOkPressed: () {}, + ); + }), + ], + ).paddingSymmetrical(20.w, 24.h) + : Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Blood Sugar".needTranslation.toText16(weight: FontWeight.w600), + "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: Color(0xFF8F9AA3), + ) + ], + ), + ), + SizedBox( + width: 12.w, + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).paddingAll(16.w)) + .onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodSugar), + ), + ); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Blood Cholesterol".needTranslation.toText16(weight: FontWeight.w600), + "Monitor your cholesterol levels, track your LDL, HDL, and triglycerides. Get personalized recommendations for a healthy heart." + .needTranslation + .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + ], + ), + ), + SizedBox(width: 12.w), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).paddingAll(16.w)) + .onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodCholesterol), + ), + ); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Triglycerides Fat Blood".needTranslation.toText16(weight: FontWeight.w600), + "Manage triglycerides, a key blood fat. Understand levels, diet impacts, and heart health strategies." + .needTranslation + .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + ], + ), + ), + SizedBox(width: 12.w), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).paddingAll(16.w)) + .onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.triglycerides), + ), + ); + }), + ], + ).paddingSymmetrical(20.w, 24.h)); + } + + Widget showCalculatorsItems({required HealthCalculatorEnum type}) { + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 4 icons per row + crossAxisSpacing: 16.w, + mainAxisSpacing: 16.w, + childAspectRatio: 0.80), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return HealthCard( + icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, + labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, + onTap: () { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage( + calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, + ), + ), + ); + }, + ); + }, + ); + } + + final List generalHealthServices = [ + HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi), + HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories), + HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr), + HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight), + HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat), + HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat), + ]; + + final List womenHealthServices = [ + HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation), + HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate), + ]; +} + +class HealthComponentModel { + String title; + String? subTitle; + String icon; + Color? iconColor; + Color? bgColor; + Color? textColor; + HealthCalculatorsTypeEnum type; + + HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type}); +} diff --git a/lib/presentation/health_calculators/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bf.dart rename to lib/presentation/health_calculators_and_converts/widgets/bf.dart index bbab9a5..3a37036 100644 --- a/lib/presentation/health_calculators/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'dart:math'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,7 +10,6 @@ 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/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; class BodyFatWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart new file mode 100644 index 0000000..161f318 --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class BloodCholesterolWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const BloodCholesterolWidget({super.key, this.onChange}); + + @override + State createState() => _BloodCholesterolWidgetState(); +} + +class _BloodCholesterolWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearBloodCholesterol(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + final mgdlText = provider.cholMgdlValue ?? ''; + final mmolText = provider.cholMmolValue ?? ''; + + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "120", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onBloodCholesterolChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchBloodCholesterolValues(); + if (provider.cholActiveUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + _buildInputField( + label: "MMOL/L", + hint: "6.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + + provider.onBloodCholesterolChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + const Divider(height: 1, color: Color(0xFFEEEEEE)), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), + SizedBox(width: 12.w), + Expanded( + child: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + /// Reusable Input Field + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + maxLines: 1, + onChanged: onChanged, + cursorHeight: 35.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, + height: 1.h, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart new file mode 100644 index 0000000..86b4c1e --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class BloodSugarWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const BloodSugarWidget({super.key, this.onChange}); + + @override + State createState() => _BloodSugarWidgetState(); +} + +class _BloodSugarWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearBloodSugar(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + final mgdlText = provider.mgdlValue ?? ''; + final mmolText = provider.mmolValue ?? ''; + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "120", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onBloodSugarChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchBloodSugarValues(); + if (provider.activeUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + _buildInputField( + label: "MMOL/L", + hint: "6.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + + provider.onBloodSugarChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + const Divider(height: 1, color: Color(0xFFEEEEEE)), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.globe, + width: 18.w, + height: 18.w, + ), + SizedBox(width: 12.w), + Expanded( + child: "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)".toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + /// Reusable Input Field + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + maxLines: 1, + onChanged: onChanged, + cursorHeight: 35.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, + height: 1.h, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/health_calculators/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bmi.dart rename to lib/presentation/health_calculators_and_converts/widgets/bmi.dart index be87d0d..49b77c0 100644 --- a/lib/presentation/health_calculators/widgets/bmi.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart @@ -8,7 +8,7 @@ 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/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class BMIWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bmr.dart rename to lib/presentation/health_calculators_and_converts/widgets/bmr.dart index 771191e..138fd3e 100644 --- a/lib/presentation/health_calculators/widgets/bmr.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart @@ -10,7 +10,7 @@ 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/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class BMRWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/calories.dart rename to lib/presentation/health_calculators_and_converts/widgets/calories.dart index 0d7aab7..bfaad3e 100644 --- a/lib/presentation/health_calculators/widgets/calories.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart @@ -10,7 +10,7 @@ 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/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class CaloriesWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart similarity index 98% rename from lib/presentation/health_calculators/widgets/crabs.dart rename to lib/presentation/health_calculators_and_converts/widgets/crabs.dart index 3fa1bdf..5fe1215 100644 --- a/lib/presentation/health_calculators/widgets/crabs.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart @@ -7,11 +7,11 @@ 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/presentation/health_calculators/health_calculator_detailed_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class CrabsWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/dduedate.dart b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart similarity index 94% rename from lib/presentation/health_calculators/widgets/dduedate.dart rename to lib/presentation/health_calculators_and_converts/widgets/dduedate.dart index 91057aa..15c0a0c 100644 --- a/lib/presentation/health_calculators/widgets/dduedate.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart @@ -8,7 +8,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class DeliveryDueDWidget extends StatefulWidget { final Function(dynamic result)? onChange; @@ -35,7 +35,7 @@ class _DeliveryDueDWidgetState extends State { child: Column( children: [ TextInputWidget( - labelText: "Date", + labelText: "Last Period Date", hintText: "11 July, 1994".needTranslation, controller: _date, focusNode: FocusNode(), @@ -56,8 +56,6 @@ class _DeliveryDueDWidgetState extends State { selectedDate = Utils.formatDateToDisplay(val); _date.text = selectedDate; }); - - // provider expects ISO date string to calculate due date provider.calculateDueDate(isoDateString: val); if (widget.onChange != null) { diff --git a/lib/presentation/health_calculators/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart similarity index 100% rename from lib/presentation/health_calculators/widgets/health_card.dart rename to lib/presentation/health_calculators_and_converts/widgets/health_card.dart diff --git a/lib/presentation/health_calculators/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/ibw.dart rename to lib/presentation/health_calculators_and_converts/widgets/ibw.dart index 0e3f725..5f67511 100644 --- a/lib/presentation/health_calculators/widgets/ibw.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart @@ -8,7 +8,7 @@ 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/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class IdealBodyWeightWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart similarity index 91% rename from lib/presentation/health_calculators/widgets/ovulation.dart rename to lib/presentation/health_calculators_and_converts/widgets/ovulation.dart index 476bf72..de209cb 100644 --- a/lib/presentation/health_calculators/widgets/ovulation.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart @@ -8,7 +8,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class OvulationWidget extends StatefulWidget { Function(dynamic result)? onChange; @@ -78,7 +78,7 @@ class _OvulationWidgetState extends State { onChange: (val) { if (val == null) return; setState(() { - _ageController.text = selectedDate = Utils.formatDateToDisplay(val!); + _ageController.text = selectedDate = Utils.formatDateToDisplay(val); }); _onInputChanged(); }, @@ -109,7 +109,7 @@ class _OvulationWidgetState extends State { border: InputBorder.none, contentPadding: EdgeInsets.zero, isCollapsed: true, - hintText: '15', + hintText: '28', hintStyle: TextStyle(color: Colors.grey), ), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), @@ -146,7 +146,7 @@ class _OvulationWidgetState extends State { border: InputBorder.none, contentPadding: EdgeInsets.zero, isCollapsed: true, - hintText: '10', + hintText: '14', hintStyle: TextStyle(color: Colors.grey), ), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), @@ -157,17 +157,6 @@ class _OvulationWidgetState extends State { ), ], ).paddingSymmetrical(0.w, 16.w), - if (provider.ovulationResult != null) ...[ - Divider(height: 1, color: Color(0xFFEEEEEE)), - // show result UI same as before; keeping unchanged design - Row( - children: [ - Expanded(child: Text('Last Period: ${provider.ovulationResult!['lastPeriodDate'] ?? ''}')), - Expanded(child: Text('Ovulation: ${provider.ovulationResult!['ovulationDate'] ?? ''}')), - ], - ).paddingSymmetrical(0.w, 16.w), - ], - Divider(height: 1, color: Color(0xFFEEEEEE)), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart new file mode 100644 index 0000000..17ad1f9 --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class TriglyceridesWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const TriglyceridesWidget({super.key, this.onChange}); + + @override + State createState() => _TriglyceridesWidgetState(); +} + +class _TriglyceridesWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearTriglycerides(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + + final mgdlText = provider.triMgdlValue ?? ''; + final mmolText = provider.triMmolValue ?? ''; + + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "150", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onTriglyceridesChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchTriglyceridesValues(); + + if (provider.triActiveUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + + _buildInputField( + label: "MMOL/L", + hint: "1.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onTriglyceridesChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + + const Divider(height: 1, color: Color(0xFFEEEEEE)), + + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.globe, + width: 18.w, + height: 18.w, + ), + SizedBox(width: 12.w), + Expanded( + child: + "Convert triglyceride values between mg/dL and mmol/L." + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + + keyboardType: + const TextInputType.numberWithOptions(decimal: true), + onChanged: onChanged, + cursorHeight: 35.h, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index de7cf2c..bfd9f30 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -17,6 +18,8 @@ import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.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'; +import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; +import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -154,7 +157,7 @@ class ServicesPage extends StatelessWidget { AppAssets.health_converters_icon, bgColor: AppColors.whiteColor, true, - route: AppRoutes.comprehensiveCheckupPage, + route: AppRoutes.healthConvertersPage, ), HmgServicesComponentModel( 11, @@ -163,8 +166,8 @@ class ServicesPage extends StatelessWidget { AppAssets.smartwatch_icon, bgColor: AppColors.whiteColor, true, - // route: AppRoutes.smartWatches, - route: AppRoutes.huaweiHealthExample, + route: AppRoutes.smartWatches, + // route: AppRoutes.huaweiHealthExample, ), HmgServicesComponentModel( 12, @@ -199,331 +202,359 @@ class ServicesPage extends StatelessWidget { body: CollapsingListView( title: "Explore Services".needTranslation, isLeading: false, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - "Medical & Care Services".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 12.w, - mainAxisSpacing: 18.h, - childAspectRatio: 0.8, - ), - physics: NeverScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + "Medical & Care Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row + crossAxisSpacing: 12.w, + mainAxisSpacing: 18.h, + childAspectRatio: 0.8, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false); + }, + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + "HMG Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + SizedBox( + height: 350.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, shrinkWrap: true, - itemCount: hmgServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false); + padding: EdgeInsets.symmetric(horizontal: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: LargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + ), + ), + ), + ); }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), ), - SizedBox(height: 24.h), - "Personal Services".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - height: 170.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, + ), + SizedBox(height: 24.h), + "Personal Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + height: 170.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 30.w, height: 30.h), + "Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + ], + ), + Spacer(), + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }), + Spacer(), + CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 16.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: "Recharge".needTranslation, + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 12.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + }), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + height: 170.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), + "Medical Files".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + ], + ), + Spacer(), + Wrap( + spacing: -8.h, + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 28.h, + width: 28.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 28.h, + width: 28.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.maleImg, + height: 28.h, + width: 28.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], + ), + Spacer(), + CustomButton( + height: 40.h, + icon: AppAssets.add_icon, + iconSize: 16.w, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: "Add Member".needTranslation, + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 12.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: "Add Family Member".needTranslation, + message: "Please fill the below field to add a new family member to your profile".needTranslation, + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + ), + ], + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + }), + ), + ), + ], + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + "Health Tools".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount:(isFoldable || isTablet) ? 6 : 4, // 4 icons per row + crossAxisSpacing: 12.w, + mainAxisSpacing: 18.h, + childAspectRatio: 0.8, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgHealthToolServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem( + hmgHealthToolServices[index], + index, + false, + isHealthToolIcon: true, + ); + }, + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + "Support Services".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 30.w, height: 30.h), - "Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets( + icon: AppAssets.virtual_tour_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Virtual Tour".needTranslation.toText12(fontWeight: FontWeight.w500) ], ), - Spacer(), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }), - Spacer(), - CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 16.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: "Recharge".needTranslation, - borderWidth: 0.w, - fontWeight: FontWeight.w500, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 12.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, - ), - ], + ), ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + Utils.openWebView( + url: 'https://hmgwebservices.com/vt_mobile/html/index.html', + ); }), ), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - height: 170.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 30.w, height: 30.h), - "Medical Files".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets( + icon: AppAssets.car_parking_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) ], ), - Spacer(), - Wrap( - spacing: -8.h, - // runSpacing: 0.h, + ), + ), + ), + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( children: [ - Utils.buildImgWithAssets( - icon: AppAssets.babyGirlImg, - height: 28.h, - width: 28.w, - border: 1, - fit: BoxFit.contain, - borderRadius: 50.r, - ), - Utils.buildImgWithAssets( - icon: AppAssets.femaleImg, - height: 28.h, - width: 28.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - Utils.buildImgWithAssets( - icon: AppAssets.maleImg, - height: 28.h, - width: 28.w, - border: 1, - borderRadius: 50.r, + Utils.buildSvgWithAssets( + icon: AppAssets.latest_news_icon, + width: 32.w, + height: 32.h, fit: BoxFit.contain, ), + SizedBox(width: 8.w), + "Latest News".needTranslation.toText12(fontWeight: FontWeight.w500) ], ), - Spacer(), - CustomButton( - height: 40.h, - icon: AppAssets.add_icon, - iconSize: 16.w, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - text: "Add Member".needTranslation, - borderWidth: 0.w, - fontWeight: FontWeight.w500, - borderColor: Colors.transparent, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 12.f, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalFileViewModel.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: "Add Family Member".needTranslation, - message: "Please fill the below field to add a new family member to your profile".needTranslation, - onVerificationPress: () { - medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - ), - ], + ), ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - ), + Utils.openWebView( + url: 'https://x.com/HMG', ); }), ), - ), - ], - ), - SizedBox(height: 24.h), - "Health Tools".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount:(isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 12.w, - mainAxisSpacing: 18.h, - childAspectRatio: 0.8, - ), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: hmgHealthToolServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return ServiceGridViewItem( - hmgHealthToolServices[index], - index, - false, - isHealthToolIcon: true, - ); - }, - ), - SizedBox(height: 24.h), - "Support Services".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.virtual_tour_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "Virtual Tour".needTranslation.toText12(fontWeight: FontWeight.w500) - ], - ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://hmgwebservices.com/vt_mobile/html/index.html', - ); - }), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.car_parking_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "Car Parking".needTranslation.toText12(fontWeight: FontWeight.w500) - ], - ), - ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, ), - ), - ], - ), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.latest_news_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "Latest News".needTranslation.toText12(fontWeight: FontWeight.w500) - ], - ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://x.com/HMG', - ); - }), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.hmg_contact_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - "HMG Contact".needTranslation.toText12(fontWeight: FontWeight.w500) - ], - ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.hmg_contact_icon, + width: 32.w, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + "HMG Contact".needTranslation.toText12(fontWeight: FontWeight.w500) + ], ), - ).onPress(() { - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, - ); - }), - ) - ], - ) - ], - ), - SizedBox(height: 24.h), - ], - ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + ) + ], + ) + ], + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + ], ), ), ); diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 3e34a92..cc3c881 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -36,7 +36,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( - serviceName: "health_calculators", + serviceName: "health_calculators_and_converts", icon: AppAssets.health_calculators_icon, title: "Health", subtitle: "Calculators", @@ -142,44 +142,42 @@ class LandingPageData { static List getServiceCardsList = [ ServiceCardData( - icon: AppAssets.liveCareService, + serviceName: "livecare", + icon: AppAssets.small_livecare_icon, title: "LiveCare", subtitle: "Explore our app, View our services and offers", - largeCardIcon: AppAssets.livecare_icon, - backgroundColor: Colors.transparent, - iconColor: Colors.transparent, - textColor: Colors.transparent, - isBold: true, - ), - ServiceCardData( - icon: AppAssets.lab_result_icon, - title: "Dermatology", - subtitle: "Explore our app, View our services and offers", - largeCardIcon: AppAssets.livecare_icon, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, + largeCardIcon: AppAssets.liveCareService, + backgroundColor: AppColors.successColor, + iconColor: AppColors.whiteColor, isBold: false, ), - ServiceCardData( - icon: AppAssets.my_prescription_icon, + // ServiceCardData( + // icon: AppAssets.homeBottom, + // title: "Dermatology", + // subtitle: "Explore our app, View our services and offers", + // largeCardIcon: AppAssets.homeBottom, + // backgroundColor: AppColors.primaryRedColor, + // isBold: false, + // ), + ServiceCardData( + serviceName: "home_health_care", + icon: AppAssets.homeBottom, title: "Home Health Care", subtitle: "Explore our app, View our services and offers", - largeCardIcon: AppAssets.livecare_icon, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, + largeCardIcon: AppAssets.homeHealthCareService, + backgroundColor: AppColors.primaryRedColor, + iconColor: AppColors.whiteColor, isBold: false, ), ServiceCardData( - icon: AppAssets.insurance_update_icon, + serviceName: "pharmacy", + icon: AppAssets.pharmacy_icon, //359846 title: "Pharmacy", subtitle: "Explore our app, View our services and offers", - largeCardIcon: AppAssets.livecare_icon, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, + largeCardIcon: AppAssets.pharmacyService, + backgroundColor: AppColors.pharmacyBGColor, + iconColor: null, + isBold: true, ), ]; } diff --git a/lib/presentation/home/data/service_card_data.dart b/lib/presentation/home/data/service_card_data.dart index 49e7e3d..3bc22d7 100644 --- a/lib/presentation/home/data/service_card_data.dart +++ b/lib/presentation/home/data/service_card_data.dart @@ -7,7 +7,7 @@ class ServiceCardData { final String icon; final String title; final String subtitle; - final Color iconColor; + final Color? iconColor; final Color textColor; final Color backgroundColor; final bool isBold; @@ -22,6 +22,6 @@ class ServiceCardData { this.backgroundColor = AppColors.whiteColor, this.iconColor = AppColors.blackColor, this.textColor = AppColors.blackColor, - this.isBold = false, + this.isBold = false }); } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index ef20e8e..b457ad9 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -109,6 +109,7 @@ class _LandingPageState extends State { prescriptionsViewModel.initPrescriptionsViewModel(); insuranceViewModel.initInsuranceProvider(); emergencyServicesViewModel.checkPatientERAdvanceBalance(); + myAppointmentsViewModel.getPatientAppointmentQueueDetails(); appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, @@ -307,15 +308,15 @@ class _LandingPageState extends State { Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), ], ), - SizedBox(height: 10.h), - "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), SizedBox(height: 8.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 2.h), "Thank you for your patience, here is your queue number." .needTranslation .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 8.h), myAppointmentsVM.currentPatientQueueDetails.queueNo!.toText28(isBold: true), - SizedBox(height: 12.h), + SizedBox(height: 6.h), myAppointmentsVM.patientQueueDetailsList.isNotEmpty ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -339,7 +340,7 @@ class _LandingPageState extends State { ), ], ) : SizedBox(height: 12.h), - SizedBox(height: 8.h), + SizedBox(height: 5.h), CustomButton( text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), onPressed: () {}, @@ -481,6 +482,7 @@ class _LandingPageState extends State { CustomButton( text: LocaleKeys.bookAppo.tr(context: context), onPressed: () { + getIt.get().onTabChanged(0); Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); }, backgroundColor: Color(0xffFEE9EA), @@ -597,7 +599,7 @@ class _LandingPageState extends State { icon: LandingPageData.getLoggedInServiceCardsList[index].icon, title: LandingPageData.getLoggedInServiceCardsList[index].title, subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, @@ -638,7 +640,7 @@ class _LandingPageState extends State { icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, title: LandingPageData.getNotLoggedInServiceCardsList[index].title, subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, @@ -683,6 +685,7 @@ class _LandingPageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: LargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], image: LandingPageData.getServiceCardsList[index].icon, title: LandingPageData.getServiceCardsList[index].title, subtitle: LandingPageData.getServiceCardsList[index].subtitle, @@ -692,7 +695,7 @@ class _LandingPageState extends State { ), ); }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.w), + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), ), ), appState.isAuthenticated ? HabibWalletCard() : SizedBox(), diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 7fbcdb3..6ec8c3a 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/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'; @@ -41,6 +42,7 @@ class _LandingNavigationState extends State { onTap: (index) { setState(() => _currentIndex = index); if (_currentIndex == 2) { + getIt.get().onTabChanged(0); context.navigateWithName(AppRoutes.bookAppointmentPage); return; } diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index 7a155df..0e02a5e 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -1,16 +1,26 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.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/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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/data/service_card_data.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.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:url_launcher/url_launcher.dart'; import '../../../core/utils/utils.dart'; import '../../../theme/colors.dart'; class LargeServiceCard extends StatelessWidget { + final ServiceCardData serviceCardData; final String image; final String icon; final String title; @@ -18,6 +28,7 @@ class LargeServiceCard extends StatelessWidget { const LargeServiceCard({ super.key, + required this.serviceCardData, this.image = "", this.icon = "", this.title = "", @@ -27,37 +38,112 @@ class LargeServiceCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - width: 150.w, - padding: EdgeInsets.symmetric(horizontal: 3.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.transparent, borderRadius: 16.r), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, + height: 350.h, + width: 230.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 24.r), + child: Stack( children: [ - Image.asset(AppAssets.liveCareService, width: 220.w, fit: BoxFit.contain), - SizedBox(height: 10.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: icon, width: 24.w, height: 24.h), - Flexible(child: title.toText14(color: AppColors.blackColor, isBold: true, textOverflow: TextOverflow.clip, maxlines: 1)), - ], + ClipRRect( + borderRadius: BorderRadius.circular(24.r), + child: Image.asset( + serviceCardData.largeCardIcon, + fit: BoxFit.cover, + ), ), - subtitle.toText11(color: AppColors.blackColor), - SizedBox(height: 10.h), - CustomButton( - text: LocaleKeys.bookNow.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.borderOnlyColor, - borderColor: AppColors.borderOnlyColor, - textColor: AppColors.whiteColor, - fontSize: 14.f, - fontWeight: FontWeight.bold, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), - height: 40.h, + Positioned( + bottom: 0.0, // Positions the child 0 logical pixels from the bottom + left: 0.0, + right: 0.0, + child: Container( + height: 180.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only( + bottomLeft: Radius.circular(24.r), + bottomRight: Radius.circular(24.r), + ), + ), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 48.h, + width: 48.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: serviceCardData.backgroundColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, + iconColor: serviceCardData.iconColor, + fit: BoxFit.contain, + ), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + serviceCardData.title.toText14(isBold: true, color: AppColors.textColor), + SizedBox(width: 6.h), + serviceCardData.subtitle.toText14(weight: FontWeight.w500, color: AppColors.textColorLight), + ], + ), + ), + ], + ).paddingSymmetrical(16.w, 20.h), + CustomButton( + text: serviceCardData.isBold ? "Visit Pharmacy Online".needTranslation : LocaleKeys.bookNow.tr(context: context), + onPressed: () { + handleOnTap(); + }, + backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor, + borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor, + textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + height: 40.h, + ).paddingSymmetrical(16.w, 0.h), + ], + ), + ), ), ], ), ); } + + void handleOnTap() { + switch (serviceCardData.serviceName) { + case "livecare": + { + getIt.get().onTabChanged(1); + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute( + page: BookAppointmentPage(), + ), + ); + } + case "home_health_care": + { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: HhcProceduresPage(), + ), + ); + } + case "pharmacy": + { + Uri uri = Uri.parse(PHARMACY_REDIRECT_URL); + launchUrl(uri, mode: LaunchMode.externalApplication); + } + } + } } diff --git a/lib/presentation/home_health_care/hhc_order_detail_page.dart b/lib/presentation/home_health_care/hhc_order_detail_page.dart index 21b0def..92c93d1 100644 --- a/lib/presentation/home_health_care/hhc_order_detail_page.dart +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -10,10 +10,8 @@ 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/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; -import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:provider/provider.dart'; @@ -75,7 +73,6 @@ class _HhcOrderDetailPageState extends State { Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) { final statusColor = _getStatusColor(order.statusId); - final canCancel = order.statusId == 1 || order.statusId == 2; return AnimatedContainer( duration: Duration(milliseconds: 300), @@ -150,27 +147,27 @@ class _HhcOrderDetailPageState extends State { ], ), - // Cancel Button - if (canCancel || isLoading) ...[ - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: "Cancel Order".needTranslation, - onPressed: isLoading ? () {} : () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 10.r, - height: 44.h, - ).toShimmer2(isShow: isLoading), - ), - ], - ), - ] + // // Cancel Button + // if (canCancel || isLoading) ...[ + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: "Cancel Order".needTranslation, + // onPressed: isLoading ? () {} : () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + // backgroundColor: AppColors.primaryRedColor, + // borderColor: AppColors.primaryRedColor, + // textColor: AppColors.whiteColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 10.r, + // height: 44.h, + // ).toShimmer2(isShow: isLoading), + // ), + // ], + // ), + // ] ], ), ), @@ -219,6 +216,7 @@ class _HhcOrderDetailPageState extends State { } return ListView.separated( + padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: viewModel.hhcOrdersList.length, diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index bdc2e45..41d8a2f 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/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/hmg_services/hmg_services_view_model.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; @@ -61,28 +63,9 @@ class _HhcProceduresPageState extends State { } Widget _buildPendingOrderCard(GetCMCAllOrdersResponseModel order) { - int status = order.statusId ?? 0; - String statusDisp = order.statusText ?? ""; - Color statusColor; - - if (status == 1) { - // pending - statusColor = AppColors.statusPendingColor; - } else if (status == 2) { - // processing - statusColor = AppColors.statusProcessingColor; - } else if (status == 3) { - // completed - statusColor = AppColors.statusCompletedColor; - } else { - // cancel / rejected - statusColor = AppColors.statusRejectedColor; - } - final canCancel = order.statusId == 1 || order.statusId == 2; final isArabic = getIt.get().isArabic(); - // Extract services from orderselectedservice List selectedServices = []; if (order.orderselectedservice != null) { if (order.orderselectedservice is List) { @@ -108,16 +91,12 @@ class _HhcProceduresPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), - decoration: BoxDecoration( - color: statusColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8.r), - ), - child: statusDisp.toText12( - color: statusColor, - fontWeight: FontWeight.w600, - ), + Row( + children: [ + "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + SizedBox(width: 4.w), + "${order.iD ?? '-'}".toText16(isBold: true), + ], ), SizedBox(width: 8.w), if (order.created != null) @@ -127,17 +106,7 @@ class _HhcProceduresPageState extends State { ), ], ), - - SizedBox(height: 16.h), - - // Request ID - Row( - children: [ - "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), - SizedBox(width: 4.w), - "${order.iD ?? '-'}".toText16(isBold: true), - ], - ), + SizedBox(height: 8.h), // Services List if (selectedServices.isNotEmpty) ...[ @@ -212,13 +181,13 @@ class _HhcProceduresPageState extends State { ], ), ); - }).toList(), + }), ], ), ), ], - SizedBox(height: 12.h), + SizedBox(height: 8.h), // Info message Container( @@ -274,99 +243,148 @@ class _HhcProceduresPageState extends State { ); } - Widget _buildServiceSelectionList(List services) { - if (services.isEmpty) { - return Center( - child: Padding( - padding: EdgeInsets.all(24.h), - child: Text( - 'No services available'.needTranslation, - style: TextStyle( - fontSize: 16.h, - color: AppColors.greyTextColor, - ), - ), - ), - ); - } - - return Consumer( - builder: (context, viewModel, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 8.h), - SizedBox(height: 16.h), - Text( - 'Select Services'.needTranslation, - style: TextStyle( - fontSize: 20.h, - fontWeight: FontWeight.w700, - color: AppColors.blackColor, - letterSpacing: -0.8, + _buildServicesListBottomsSheet(List services) { + showCommonBottomSheetWithoutHeight( + hasBottomPadding: false, + padding: EdgeInsets.only(top: 24.h), + context, + title: 'Select Services'.needTranslation, + isCloseButtonVisible: true, + isDismissible: true, + callBackFunc: () {}, + child: services.isEmpty + ? Center( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Text( + 'No services available'.needTranslation, + style: TextStyle( + fontSize: 16.h, + color: AppColors.greyTextColor, + ), + ), ), - ).paddingOnly(left: 16.w, right: 16.w), - SizedBox(height: 12.h), - ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 16.w), - itemCount: services.length, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - final service = services[index]; - final isSelected = viewModel.isHhcServiceSelected(service); - final isArabic = getIt.get().isArabic(); - final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + ) + : Consumer( + builder: (context, hmgServicesViewModel, child) { + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + final hasSelectedServices = pendingOrder == null && hmgServicesViewModel.selectedHhcServices.isNotEmpty; - return AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.only(bottom: 12.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 16.r, - hasShadow: true, - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => viewModel.toggleHhcServiceSelection(service), - borderRadius: BorderRadius.circular(16.r), - child: Container( - padding: EdgeInsets.all(16.w), - child: Row( - children: [ - Checkbox( - value: isSelected, - onChanged: (v) => viewModel.toggleHhcServiceSelection(service), - activeColor: AppColors.primaryRedColor, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: VisualDensity.compact, + return Stack( + children: [ + // Services List + ConstrainedBox( + constraints: BoxConstraints(maxHeight: SizeUtils.height * 0.7), + child: ListView.separated( + separatorBuilder: (context, index) => Divider(color: AppColors.dividerColor).paddingSymmetrical(16.w, 0), + padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: hasSelectedServices ? 160.h : 8.h), + shrinkWrap: true, + itemCount: services.length, + itemBuilder: (context, index) { + final service = services[index]; + final isSelected = hmgServicesViewModel.isHhcServiceSelected(service); + final isArabic = getIt.get().isArabic(); + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: InkWell( + onTap: () => hmgServicesViewModel.toggleHhcServiceSelection(service), + borderRadius: BorderRadius.circular(16.r), + child: Container( + padding: EdgeInsets.all(8.w), + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + width: 24.w, + height: 24.w, + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(5.r), + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : AppColors.borderGrayColor, + width: 1.w, + ), + ), + child: isSelected ? Icon(Icons.check, size: 18.f, color: AppColors.whiteColor) : null, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + serviceName.toText16( + weight: FontWeight.w500, + color: AppColors.blackColor, + maxlines: 2, + ), + ], + ), + ), + ], + ), + ), ), - SizedBox(width: 12.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + }, + ), + ), + + if (hasSelectedServices) + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 24.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - serviceName.toText16( - weight: FontWeight.w400, - color: AppColors.blackColor, - maxlines: 2, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Services".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + "${hmgServicesViewModel.selectedHhcServices.length} service(s) selected".toText14( + isBold: true, + weight: FontWeight.bold, + ), + ], ), ], ), - ), - ], + SizedBox(height: 16.h), + CustomButton( + borderWidth: 0, + text: "Next".needTranslation, + onPressed: () { + Navigator.pop(context); + _proceedWithSelectedService(); + }, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + ), + ], + ), ), ), - ), - ), + ], ); }, ), - ], - ); - }, ); } @@ -426,77 +444,70 @@ class _HhcProceduresPageState extends State { @override Widget build(BuildContext context) { - return CollapsingListView( - title: "Home Health Care".needTranslation, - history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), - bottomChild: Consumer( - builder: (context, hmgServicesViewModel, child) { - if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) return SizedBox.shrink(); - final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); - if (pendingOrder == null && hmgServicesViewModel.selectedHhcServices.isNotEmpty) { - return SafeArea( - top: false, - child: Container( - color: AppColors.whiteColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Selected Services Summary Widget - Container( - margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h), - padding: EdgeInsets.all(16.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Selected Services".needTranslation.toText12( - color: AppColors.textColorLight, - fontWeight: FontWeight.w600, - ), - "${hmgServicesViewModel.selectedHhcServices.length} service(s) selected".toText14( - isBold: true, - weight: FontWeight.bold, - ), - ], + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Home Health Care".needTranslation, + history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), + child: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { + return _buildLoadingShimmer(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder != null) { + return _buildPendingOrderCard(pendingOrder); + } else { + return Column( + children: [ + Center( + child: Utils.getNoDataWidget( + context, + noDataText: "You have no pending requests.".needTranslation, ), - ], - ), - ), - // Next Button - Padding( - padding: EdgeInsets.only(left: 16.w, right: 16.w), - child: CustomButton( - borderWidth: 0, - text: "Next".needTranslation, - onPressed: _proceedWithSelectedService, - textColor: AppColors.whiteColor, - borderRadius: 12.r, - borderColor: Colors.transparent, - padding: EdgeInsets.symmetric(vertical: 14.h), - ), - ), - ], - ), + ), + ], + ); + } + }, ), - ); - } - return SizedBox.shrink(); - }, - ), - child: Consumer( - builder: (context, hmgServicesViewModel, child) { - if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { - return _buildLoadingShimmer(); - } - final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); - if (pendingOrder != null) { - return _buildPendingOrderCard(pendingOrder); - } else { - return _buildServiceSelectionList(hmgServicesViewModel.hhcServicesList); - } - }, + ), + ), + Consumer( + builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { + return SizedBox.shrink(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder == null) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 24.h, top: 24.h), + child: CustomButton( + borderWidth: 0, + text: "Create new request".needTranslation, + onPressed: () => _buildServicesListBottomsSheet(hmgServicesViewModel.hhcServicesList), + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + + return SizedBox.shrink(); + }, + ), + ], ), ); } diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart index 8e9ba90..37410e2 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -15,6 +15,7 @@ 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/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; @@ -68,40 +69,26 @@ class _HhcSelectionReviewPageState extends State { Widget _buildSelectedServicesCard(bool isArabic) { return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 16.r, - ), + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), padding: EdgeInsets.all(16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "Selected Services".needTranslation.toText14( weight: FontWeight.w600, - color: AppColors.greyTextColor, + color: AppColors.textColor, letterSpacing: -0.4, ), SizedBox(height: 12.h), - ...widget.selectedServices.map((service) { - final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); - final price = service.priceTotal ?? 0.0; - return Padding( - padding: EdgeInsets.only(bottom: 4.h), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: serviceName.toText14( - weight: FontWeight.w600, - color: AppColors.blackColor, - letterSpacing: -0.5, - maxlines: 2, - ), - ), - ], - ), - ); - }), + Wrap( + spacing: 8.w, + runSpacing: 12.w, + children: widget.selectedServices.map((service) { + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + return AppCustomChipWidget(labelText: serviceName.needTranslation); + }).toList(), + ), ], ), ); @@ -117,8 +104,8 @@ class _HhcSelectionReviewPageState extends State { return SizedBox.shrink(); } - final double lat = mapCapturedLocation.lat ?? 0.0; - final double lng = mapCapturedLocation.lng ?? 0.0; + final double lat = mapCapturedLocation?.lat ?? 0.0; + final double lng = mapCapturedLocation?.lng ?? 0.0; if (lat == 0.0 || lng == 0.0) return SizedBox.shrink(); @@ -132,36 +119,25 @@ class _HhcSelectionReviewPageState extends State { latitude: lat, longitude: lng, address: address, - title: "Service Location".needTranslation, onDirectionsTap: () => _launchDirectionsToLocation(lat, lng, address), ); } Widget _buildBottomButton() { - return SafeArea( - top: false, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), - decoration: BoxDecoration( - color: AppColors.whiteColor, - boxShadow: [ - BoxShadow( - color: Color.fromARGB(13, 0, 0, 0), - blurRadius: 8, - offset: Offset(0, -2), - ), - ], - ), - child: CustomButton( - text: "Confirm".needTranslation, - onPressed: _handleConfirm, - textColor: AppColors.whiteColor, - backgroundColor: AppColors.successColor, - borderRadius: 12.r, - borderColor: Colors.transparent, - borderWidth: 0, - padding: EdgeInsets.symmetric(vertical: 14.h), - ), + return Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 24.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: CustomButton( + borderWidth: 0, + text: "Confirm".needTranslation, + onPressed: () => _handleConfirm(), + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, ), ); } diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index cdd9a2e..bacf5e9 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -74,7 +74,7 @@ class _InsuranceHomePageState extends State { padding: EdgeInsets.only(top: 24.h), child: PatientInsuranceCard( insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))).paddingSymmetrical(24.w, 0.h), ) : Padding( padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 34a3d2f..1b60e03 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/active_medication/active_medica import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; @@ -457,6 +458,7 @@ class _MedicalFilePageState extends State { CustomButton( text: LocaleKeys.bookAppo.tr(context: context), onPressed: () { + getIt.get().onTabChanged(0); Navigator.of(context).push( CustomPageRoute( page: BookAppointmentPage(), @@ -497,8 +499,36 @@ class _MedicalFilePageState extends State { onRescheduleTap: () { openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); }, - onAskDoctorTap: () {}, - )), + 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"); + } + }); + }, + ), + ), ), ), ); diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index eef73eb..f31c90b 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -28,59 +28,24 @@ class LabRadCard extends StatelessWidget { AppState appState = getIt.get(); return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 18.r, hasShadow: false), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Utils.buildSvgWithAssets( - icon: icon, - width: 40.w, - height: 40.h, - fit: BoxFit.cover, - ).toShimmer2(isShow: false, radius: 12.r), - SizedBox(width: 8.w), - Flexible( - child: labelText.toText12(isBold: true, maxLine: 2), - ), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 10.w, height: 10.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ), - ], + Utils.buildSvgWithAssets( + icon: icon, + width: 40.w, + height: 40.h, + fit: BoxFit.cover, + ).toShimmer2(isShow: false, radius: 12.r), + SizedBox(width: 8.w), + Flexible( + child: labelText.toText12(isBold: true, maxLine: 2), + ), + SizedBox(width: 12.w), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 10.w, height: 10.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ), - // SizedBox(height: 16.h), - // labOrderTests.isNotEmpty - // ? ListView.separated( - // scrollDirection: Axis.vertical, - // padding: EdgeInsets.zero, - // physics: NeverScrollableScrollPhysics(), - // shrinkWrap: true, - // itemBuilder: (cxt, index) { - // return labOrderTests[index] - // .toText12(isBold: true, maxLine: 1) - // .toShimmer2(isShow: false, radius: 6.r, height: 24.h, width: 120.w) - // .toShimmer2(isShow: isLoading); - // }, - // separatorBuilder: (cxt, index) => SizedBox(height: 8.h), - // itemCount: 3, - // ) - // : "You don't have any records yet".needTranslation.toText13( - // color: AppColors.greyTextColor, isCenter: true), - // SizedBox(height: 16.h), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // SizedBox.shrink(), - // Transform.flip( - // flipX: appState.isArabic(), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor) - // .toShimmer2(isShow: false, radius: 12.r), - // ), - // ], - // ) ], ).paddingAll(12.w), ); diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index 71abcb7..4047a53 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -10,12 +10,11 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_report_request_page.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/widgets/patient_medical_report_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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/widgets/custom_tab_bar.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'; @@ -53,7 +52,8 @@ class _MedicalReportsPageState extends State { medicalFileViewModel.onMedicalReportTabChange(0); }, backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + borderColor: + medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, @@ -68,7 +68,8 @@ class _MedicalReportsPageState extends State { medicalFileViewModel.onMedicalReportTabChange(1); }, backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + borderColor: + medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, @@ -83,7 +84,8 @@ class _MedicalReportsPageState extends State { medicalFileViewModel.onMedicalReportTabChange(2); }, backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + borderColor: + medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, @@ -131,7 +133,8 @@ class _MedicalReportsPageState extends State { child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: PatientMedicalReportCard( patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index], medicalFileViewModel: medicalFileVM, @@ -141,7 +144,8 @@ class _MedicalReportsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation).paddingSymmetrical(24.h, 24.h); + : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation) + .paddingSymmetrical(24.h, 24.h); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), @@ -188,11 +192,10 @@ class _MedicalReportsPageState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 45.h, + borderRadius: 12.r, + height: 46.h, icon: AppAssets.requests, iconColor: AppColors.whiteColor, iconSize: 20.h, @@ -218,7 +221,9 @@ class _MedicalReportsPageState extends State { LoaderBottomSheet.showLoader(); await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: "Your medical report request has been successfully submitted.".needTranslation), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: "Your medical report request has been successfully submitted.".needTranslation), + callBackFunc: () { medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.onMedicalReportTabChange(0); medicalFileViewModel.getPatientMedicalReportList(); diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 357f5f5..1c3644b 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -4,15 +4,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/core/app_assets.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/health_dashboard/health_dashboard.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:url_launcher/url_launcher.dart'; class SmartwatchInstructionsPage extends StatelessWidget { const SmartwatchInstructionsPage({super.key}); @@ -178,19 +181,20 @@ class SmartwatchInstructionsPage extends StatelessWidget { await Health().getHealthConnectSdkStatus().then((val) { print('Health Connect SDK Status: $val'); if (val == HealthConnectSdkStatus.sdkAvailable) { - // Navigator.of(context).push( - // FadePage( - // page: HealthDashboard(), // Replace with the actual vital signs page - // // page: HealthApp(), // Replace with the actual vital signs page - // ), - // ); + Navigator.of(context).push( + CustomPageRoute( + page: HealthDashboard(), + ), + ); } else { - // Utils.showAppDialog(context, LocaleKeys.error.tr(context: context), LocaleKeys.healthConnectNotInstalled.tr(), () { - // Navigator.pop(context); - // Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); - // launchUrl(uri, mode: LaunchMode.externalApplication); - // }); - // return; + getIt.get().showErrorBottomSheet( + message: "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.".needTranslation, + onOkPressed: () { + Navigator.pop(context); + Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); + launchUrl(uri, mode: LaunchMode.externalApplication); + }); + return; } }); }, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 11c6c00..970d2b9 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; @@ -6,7 +7,7 @@ import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_p import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculators_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; @@ -40,6 +41,7 @@ class AppRoutes { static const String eReferralPage = '/erReferralPage'; static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; static const String healthCalculatorsPage = '/healthCalculatorsPage'; + static const String healthConvertersPage = '/healthConvertersPage'; static const String homeHealthCarePage = '/homeHealthCarePage'; static const String zoomCallPage = '/zoomCallPage'; static const String bloodDonationPage = '/bloodDonationPage'; @@ -97,5 +99,7 @@ class AppRoutes { child: const MonthlyReportsPage(), ), + healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator), + healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter) }; } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 635af8a..37815e7 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -33,6 +33,8 @@ class AppColors { static const Color greyTextColor = Color(0xFF8F9AA3); static const Color lightGrayBGColor = Color(0x142E3039); + static const Color pharmacyBGColor = Color(0xFF359846); + static const lightGreenColor = Color(0xFF0ccedde); static const textGreenColor = Color(0xFF18C273); static const Color ratingColorYellow = Color(0xFFFFAF15); diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 7329de9..ca40c32 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -6,6 +6,7 @@ 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/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/context_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'; @@ -47,6 +48,8 @@ class CollapsingListView extends StatelessWidget { this.physics, }); + final ScrollController _controller = ScrollController(); + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -56,15 +59,31 @@ class CollapsingListView extends StatelessWidget { children: [ CustomScrollView( physics: physics, + controller: _controller, slivers: [ SliverAppBar( - automaticallyImplyLeading: false, + automaticallyImplyLeading: isLeading, pinned: true, - expandedHeight: MediaQuery.of(context).size.height * 0.11.h, - stretch: true, + // toolbarHeight: isLeading ? 24.h : kToolbarHeight, + leadingWidth: isLeading ? null : double.infinity, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, backgroundColor: AppColors.bgScaffoldColor, + bottom: isLeading + ? ScrollAnimatedTitle( + title: title, + showBack: true, + controller: _controller, + search: search, + report: report, + logout: logout, + history: history, + instructions: instructions, + requests: requests, + bottomChild: bottomChild, + trailing: trailing, + ) + : null, leading: isLeading ? Transform.flip( flipX: appState.isArabic(), @@ -81,68 +100,19 @@ class CollapsingListView extends StatelessWidget { highlightColor: Colors.transparent, ), ) - : SizedBox.shrink(), - flexibleSpace: LayoutBuilder( - builder: (context, constraints) { - final double maxHeight = 100.h; - final double minHeight = kToolbarHeight; - double t = (constraints.maxHeight - minHeight) / (maxHeight - minHeight); - t = t - 1; - if (t < 0.7) t = 0.7; - t = t.clamp(0.0, 1.0); - - final double fontSize = lerpDouble(14, 18, t)!; - final double bottomPadding = lerpDouble(0, 0, t)!; - final double leftPadding = lerpDouble(150, 24, t)!; - - return Stack( - children: [ - Align( - alignment: Alignment.lerp( - Alignment.center, - Alignment.bottomLeft, - t, - )!, - child: Padding( - padding: EdgeInsets.only( - left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), - child: Row( - spacing: 4.h, - children: [ - Text( - title, - maxLines: 1, - style: TextStyle( - fontSize: (27 - (5 * (2 - t))).f, - fontWeight: FontWeight.lerp( - FontWeight.w300, - FontWeight.w600, - t, - )!, - color: AppColors.blackColor, - letterSpacing: -0.5), - ).expanded, - if (logout != null) - actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), - if (report != null) - actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(report!), - if (history != null) - actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(history!), - if (instructions != null) - actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), - if (requests != null) - actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(requests!), - if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), - if (trailing != null) trailing!, - ], - )), - ), - ], - ); - }, - ), + : ScrollAnimatedTitle( + title: title, + showBack: false, + controller: _controller, + search: search, + report: report, + logout: logout, + history: history, + instructions: instructions, + requests: requests, + bottomChild: bottomChild, + trailing: trailing, + ), ), SliverList( delegate: SliverChildBuilderDelegate( @@ -158,13 +128,161 @@ class CollapsingListView extends StatelessWidget { ); } +// Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { +// return AnimatedSize( +// duration: Duration(milliseconds: 150), +// child: Container( +// height: 40.h, +// padding: EdgeInsets.all(8.w), +// margin: EdgeInsets.only(right: 24.w), +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration( +// color: AppColors.secondaryLightRedColor, +// borderRadius: 10.r, +// ), +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.center, +// mainAxisAlignment: MainAxisAlignment.center, +// mainAxisSize: MainAxisSize.min, +// spacing: 8.h, +// children: [ +// Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), +// if (t == 1) +// Text( +// title, +// style: context.dynamicTextStyle( +// color: AppColors.primaryRedColor, +// letterSpacing: -0.4, +// fontSize: (12 - (2 * (1 - t))).f, +// fontWeight: FontWeight.lerp( +// FontWeight.w300, +// FontWeight.w500, +// t, +// )!, +// ), +// ), +// ], +// ), +// ), +// ); +// } +} +// +// class ActionModel { +// bool requireT; +// +// } + +class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget { + final String title; + final bool showBack; + final ScrollController controller; + + VoidCallback? search; + VoidCallback? report; + VoidCallback? logout; + VoidCallback? history; + VoidCallback? instructions; + VoidCallback? requests; + Widget? bottomChild; + Widget? trailing; + + ScrollAnimatedTitle({ + super.key, + required this.title, + required this.controller, + required this.showBack, + this.search, + this.report, + this.logout, + this.history, + this.instructions, + this.requests, + this.bottomChild, + this.trailing, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + State createState() => _ScrollAnimatedTitleState(); +} + +class _ScrollAnimatedTitleState extends State { + static const double _maxFont = 24; + static const double _minFont = 14; + + static const double _maxHeight = 80; + static const double _minHeight = 56; + + double _fontSize = _maxFont; + + @override + void initState() { + super.initState(); + widget.controller.addListener(_onScroll); + } + + @override + void dispose() { + widget.controller.removeListener(_onScroll); + super.dispose(); + } + + double t = 0; + + void _onScroll() { + final double offset = widget.controller.offset; + + // control animation range + const double range = 120; + + final double t = (1 - (offset / range)).clamp(0.0, 1.0); + this.t = t; + setState(() { + _fontSize = _minFont + (_maxFont - _minFont) * t; + }); + } + + @override + Widget build(BuildContext context) { + final isRtl = Directionality.of(context) == TextDirection.rtl; + return Container( + height: (widget.preferredSize.height - _fontSize / 2).h, + alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), + padding: EdgeInsets.fromLTRB(24, 0, 24, 0), + child: Row( + spacing: 4.h, + children: [ + Text( + widget.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: _fontSize, + fontWeight: FontWeight.bold, + ), + ).expanded, + ...[ + if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), + 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.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), + if (widget.trailing != null) widget.trailing!, + ] + ], + ), + ); + } + Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { return AnimatedSize( duration: Duration(milliseconds: 150), child: Container( - height: 40.h, + height: 36.h + (4.h * t), padding: EdgeInsets.all(8.w), - margin: EdgeInsets.only(right: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.secondaryLightRedColor, borderRadius: 10.r, @@ -176,7 +294,7 @@ class CollapsingListView extends StatelessWidget { spacing: 8.h, children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), - if (t == 1) + if (t >= .5) Text( title, style: context.dynamicTextStyle( @@ -184,7 +302,7 @@ class CollapsingListView extends StatelessWidget { letterSpacing: -0.4, fontSize: (12 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( - FontWeight.w300, + FontWeight.w400, FontWeight.w500, t, )!, diff --git a/lib/widgets/custom_tab_bar.dart b/lib/widgets/custom_tab_bar.dart index 35aafa4..c30c070 100644 --- a/lib/widgets/custom_tab_bar.dart +++ b/lib/widgets/custom_tab_bar.dart @@ -14,7 +14,7 @@ class CustomTabBarModel { } class CustomTabBar extends StatefulWidget { - final int initialIndex = 0; + final int initialIndex; final List tabs; final Color activeTextColor; final Color activeBackgroundColor; @@ -25,6 +25,7 @@ class CustomTabBar extends StatefulWidget { const CustomTabBar({ super.key, required this.tabs, + this.initialIndex = 0, this.activeTextColor = const Color(0xff2E3039), this.inActiveTextColor = const Color(0xff898A8D), this.activeBackgroundColor = const Color(0x142E3039), @@ -41,6 +42,7 @@ class CustomTabBarState extends State { @override void initState() { + selectedIndex = widget.initialIndex; super.initState(); } diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index 4cf5eae..c0eb431 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -21,7 +21,7 @@ class LocationMapWidget extends StatelessWidget { final String address; /// The title to show above the map (e.g., "Service Location", "Hospital Location") - final String title; + final String? title; /// The zoom level for the map (default: 14) final int zoomLevel; @@ -36,9 +36,6 @@ class LocationMapWidget extends StatelessWidget { /// Whether to show the address container (default: true) final bool showAddress; - /// Whether to show the title (default: true) - final bool showTitle; - /// Custom map type (default: roadmap) final String mapType; final EdgeInsets? padding; @@ -48,12 +45,11 @@ class LocationMapWidget extends StatelessWidget { required this.latitude, required this.longitude, required this.address, - required this.title, + this.title, this.zoomLevel = 14, this.mapSize = '350x165', this.onDirectionsTap, this.showAddress = true, - this.showTitle = true, this.mapType = 'roadmap', this.padding, }); @@ -70,18 +66,15 @@ class LocationMapWidget extends StatelessWidget { "https://maps.googleapis.com/maps/api/staticmap?center=$latitude,$longitude&zoom=$zoomLevel&size=$mapSize&maptype=$mapType&markers=color:red%7C$latitude,$longitude&key=${ApiKeyConstants.googleMapsApiKey}"; return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 16.r, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), padding: padding ?? EdgeInsets.all(16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title - if (showTitle) ...[ + if (title != null) ...[ Text( - title, + title ?? "", style: TextStyle( fontSize: 16.f, fontWeight: FontWeight.w700, @@ -94,35 +87,22 @@ class LocationMapWidget extends StatelessWidget { // Address display if (showAddress) ...[ - Container( - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.h), - decoration: BoxDecoration( - color: AppColors.bgScaffoldColor, - borderRadius: BorderRadius.circular(12.r), - border: Border.all( - color: AppColors.greyColor.withAlpha(51), - width: 1, - ), - ), - child: Row( - children: [ - Icon(Icons.location_on, color: AppColors.primaryRedColor, size: 20.h), - SizedBox(width: 8.w), - Expanded( - child: Text( - address, - style: TextStyle( - fontSize: 14.f, - fontWeight: FontWeight.w500, - color: AppColors.blackColor, - letterSpacing: -0.4, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + Row( + children: [ + Expanded( + child: Text( + address, + style: TextStyle( + fontSize: 14.f, + fontWeight: FontWeight.w500, + color: AppColors.blackColor, + letterSpacing: -0.4, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - ], - ), + ), + ], ), SizedBox(height: 16.h), ],