From 1ca39e1332e2da8ce25ccb95d2067da6d8d39a69 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 9 Nov 2025 15:22:56 +0300 Subject: [PATCH] Ambulance order screens and geocoding request added --- assets/images/svg/pin_location.svg | 4 + lib/core/app_assets.dart | 1 + lib/core/dependencies.dart | 13 + lib/core/utils/utils.dart | 49 ++- lib/extensions/string_extensions.dart | 4 +- .../emergency_services_repo.dart | 87 +++- .../emergency_services_view_model.dart | 313 +++++++++++++- .../models/AmbulanceCallingPlace.dart | 4 + .../models/ambulance_direction.dart | 3 + .../models/request_model/PatientER_RC.dart | 205 +++++++++ .../PatientERTransportationMethod.dart | 56 +++ .../resp_model/ProjectAvgERWaitingTime.dart | 0 .../rrt_procedures_response_model.dart | 0 lib/features/location/GeocodeResponse.dart | 80 ++++ lib/features/location/PlaceDetails.dart | 14 + lib/features/location/PlacePrediction.dart | 11 + lib/features/location/location_repo.dart | 153 +++++++ .../location/location_view_model.dart | 120 ++++++ .../models/resp_models/hospital_model.dart | 2 + lib/main.dart | 3 + .../hospital_list_items.dart | 7 +- .../call_ambulance/call_ambulance_page.dart | 404 ++++++++++++++---- .../call_ambulance/tracking_screen.dart | 6 + .../widgets/HospitalBottomSheetBody.dart | 41 +- ...mbulance_option_selection_bottomsheet.dart | 66 +-- .../widgets/appointment_bottom_sheet.dart | 92 ++++ .../widgets/pickup_location.dart | 172 ++++++++ .../widgets/transport_option_Item.dart | 30 +- .../widgets/type_selection_widget.dart | 103 +++-- .../emergency_services_page.dart | 85 +++- .../emergency_services/nearest_er_page.dart | 2 +- .../widgets/location_input_bottom_sheet.dart | 118 +++++ .../widgets/nearestERItem.dart | 2 +- lib/services/dialog_service.dart | 8 +- lib/theme/colors.dart | 2 + lib/widgets/CustomSwitch.dart | 15 +- lib/widgets/common_bottom_sheet.dart | 2 + lib/widgets/input_widget.dart | 2 +- lib/widgets/map/HMSMap.dart | 48 ++- lib/widgets/map/map.dart | 47 +- 40 files changed, 2103 insertions(+), 271 deletions(-) create mode 100644 assets/images/svg/pin_location.svg create mode 100644 lib/features/emergency_services/models/AmbulanceCallingPlace.dart create mode 100644 lib/features/emergency_services/models/ambulance_direction.dart create mode 100644 lib/features/emergency_services/models/request_model/PatientER_RC.dart create mode 100644 lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart rename lib/features/emergency_services/{model => models}/resp_model/ProjectAvgERWaitingTime.dart (100%) rename lib/features/emergency_services/models/{resp_models => resp_model}/rrt_procedures_response_model.dart (100%) create mode 100644 lib/features/location/GeocodeResponse.dart create mode 100644 lib/features/location/PlaceDetails.dart create mode 100644 lib/features/location/PlacePrediction.dart create mode 100644 lib/features/location/location_repo.dart create mode 100644 lib/features/location/location_view_model.dart create mode 100644 lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart create mode 100644 lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart create mode 100644 lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart diff --git a/assets/images/svg/pin_location.svg b/assets/images/svg/pin_location.svg new file mode 100644 index 00000000..1a8012d5 --- /dev/null +++ b/assets/images/svg/pin_location.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 61cf2997..a64110d8 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -25,6 +25,7 @@ class AppAssets { static const String download = '$svgBasePath/download.svg'; static const String language = '$svgBasePath/language.svg'; static const String location = '$svgBasePath/location.svg'; + static const String pin_location = '$svgBasePath/pin_location.svg'; static const String whatsapp = '$svgBasePath/whatsapp.svg'; static const String card_user = '$svgBasePath/card_user.svg'; static const String habiblogo = '$svgBasePath/habiblogo.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a82a9adb..42e9ea88 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -19,6 +19,8 @@ import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_repo.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; @@ -103,6 +105,8 @@ class AppDependencies { getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton( + () => LocationRepoImpl(apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -199,6 +203,15 @@ class AppDependencies { emergencyServicesRepo: getIt(), appState: getIt(), errorHandlerService: getIt(), + appointmentRepo: getIt(), + dialogService: getIt() + ), + ); + + getIt.registerLazySingleton( + () => LocationViewModel( + locationRepo: getIt(), + errorHandlerService: getIt(), ), ); diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 491aa493..d890d36f 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -18,6 +18,8 @@ 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/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -742,10 +744,14 @@ class Utils { static Widget getPaymentAmountWithSymbol2(num habibWalletAmount, {double iconSize = 14, - Color iconColor = AppColors.textColor, + double? fontSize, + double? letterSpacing, + FontWeight? fontWeight, + Color iconColor = AppColors.textColor, Color textColor = AppColors.blackColor, bool isSaudiCurrency = true, - bool isExpanded = true}) { + bool isExpanded = true, + }) { return RichText( maxLines: 1, text: TextSpan( @@ -756,8 +762,13 @@ class Utils { child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor), ), TextSpan( - text: " $habibWalletAmount", - style: TextStyle(color: textColor, fontSize: 32.f, letterSpacing: -4, fontWeight: FontWeight.w600, height: 1), + text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), + style: TextStyle( + color: textColor, + fontSize: fontSize ?? 32.f, + letterSpacing: letterSpacing??-4, + fontWeight: fontWeight ?? FontWeight.w600, + height: 1), ), ], ), @@ -815,4 +826,34 @@ class Utils { } return isHavePrivilege; } + + ///method to be used to get the text as per the langauge of the application + static String getTextWRTCurrentLanguage( + String? englishText, String? arabicText) { + String? text = appState.isArabic() ? arabicText : englishText; + return text ?? ''; + } + + + static String formatNumberToInternationalFormat(num number, {String? currencySymbol, int decimalDigit = 0}){ + return NumberFormat.currency( + locale: 'en_US', + symbol: currencySymbol??"", + decimalDigits: decimalDigit) + .format(number); + } + + + static PatientDoctorAppointmentList? convertToPatientDoctorAppointmentList( + HospitalsModel? hospital) { + if (hospital == null) return null; + return PatientDoctorAppointmentList( + filterName: hospital.name, + distanceInKMs: hospital.distanceInKilometers?.toString(), + projectTopName: hospital.name, + projectBottomName: hospital.name, + model: hospital, + isHMC: hospital.isHMC); + } + } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 041e8b7e..1a6d1ccf 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -279,11 +279,11 @@ extension EmailValidator on String { height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight}) => Text( + Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing??-1, fontWeight: isBold ? FontWeight.bold : fontWeight??FontWeight.normal), ); Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 089e7ada..a9bebd57 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -3,14 +3,22 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'models/resp_model/PatientERTransportationMethod.dart'; + abstract class EmergencyServicesRepo { Future>>> getRRTProcedures(); Future>>> getNearestEr({int? id, int? projectID}); + + Future>>> getTransportationMethods({int? id}); + + Future>>> getProjectList(); + } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -91,4 +99,79 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getTransportationMethods({int? id}) async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.get( + "$GET_ALL_TRANSPORTATIONS_RC?patientID=$id", + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['response']['transportationservices']; + final proceduresList = list.map((item) => PatientERTransportationMethod.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: proceduresList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getProjectList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_LIST, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListProject']; + + final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 77e58239..742678ae 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -5,31 +5,73 @@ 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/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart' + show PatientERTransportationMethod; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; +import 'package:hmg_patient_app_new/routes/app_routes.dart' show AppRoutes; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.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:huawei_map/huawei_map.dart' as HMSCameraServices; import 'package:url_launcher/url_launcher.dart'; +import '../location/GeocodeResponse.dart'; +import 'models/ambulance_direction.dart'; + class EmergencyServicesViewModel extends ChangeNotifier { EmergencyServicesRepo emergencyServicesRepo; + MyAppointmentsRepo appointmentRepo; ErrorHandlerService errorHandlerService; + DialogService dialogService; + + Completer? gmsController; + Completer? hmsController; final NavigationService navServices; final LocationUtils? locationUtils; final AppState appState; + + //loading variables bool isERListLoading = false; + bool isTransportationOptionsLoading = false; + List nearestERList = []; List nearestERFilteredList = []; List RRTProceduresList = []; + List? hospitalList; + List? hmgHospitalList; + List? hmcHospitalList; + List? displayList; + HospitalsModel? selectedHospital; + FacilitySelection selectedFacility = FacilitySelection.ALL; + int hmgCount = 0; + int hmcCount = 0; + bool pickupFromInsideTheLocation = true; + + //ambulance selection data section + List transportationOptions = []; + PatientERTransportationMethod? selectedTransportOption; + AmbulanceCallingPlace callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; + AmbulanceDirection ambulanceDirection = AmbulanceDirection.ONE_WAY; + late RRTProceduresResponseModel selectedRRTProcedure; BottomSheetType bottomSheetType = BottomSheetType.FIXED; @@ -45,6 +87,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { required this.navServices, required this.locationUtils, required this.appState, + required this.appointmentRepo, + required this.dialogService, }); get isGMSAvailable @@ -52,6 +96,13 @@ class EmergencyServicesViewModel extends ChangeNotifier { return appState.isGMSAvailable; } + bool get isArabic => appState.isArabic(); + + bool haveAnAppointment = false; + + PatientAppointmentHistoryResponseModel? appointment; + + bool isMyAppointmentsLoading = false; Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { RRTProceduresList.clear(); @@ -168,17 +219,32 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void navigateTOAmbulancePage() { - locationUtils!.getLocation( - isShowConfirmDialog: true, - onSuccess: (position) { - updateBottomSheetState(BottomSheetType.FIXED); - navServices.push( - CustomPageRoute( - page: CallAmbulancePage(), direction: AxisDirection.down - ), - ); - - }); + placeValueInController(); + getProjects(); + flushData(); + selectedFacility = FacilitySelection.ALL; + + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated) { + locationUtils!.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) { + updateBottomSheetState(BottomSheetType.FIXED); + navServices.push( + CustomPageRoute( + page: CallAmbulancePage(), direction: AxisDirection.down), + ); + }); + } else { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + navServices.pushAndReplace( + AppRoutes.loginScreen + ); + }); + } } void updateBottomSheetState(BottomSheetType sheetType) { @@ -187,8 +253,229 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void setIsGMSAvailable(bool value) { + notifyListeners(); + } + + Future getTransportationOption() async { + //handle the cache if the data is present then dont fetch it in the authenticated lifecycle + + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated == false) { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + print("inside the ok button"); + navServices.pushAndReplace( + AppRoutes.loginScreen + ); + }); + return; + } + + if (transportationOptions.isNotEmpty) return; + + int? id = appState.getAuthenticatedUser()?.patientId; + LoaderBottomSheet.showLoader( + loadingText: "Getting Ambulance Transport Option".needTranslation); + + notifyListeners(); + var response = await emergencyServicesRepo.getTransportationMethods(id: id); + + response.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + }, + (apiResponse) { + transportationOptions = apiResponse.data!; + LoaderBottomSheet.hideLoader(); + notifyListeners(); + }, + ); + } + + Future getTransportationMethods() async { + int? id = appState.getAuthenticatedUser()?.patientId; + LoaderBottomSheet.showLoader( + loadingText: "Getting Ambulance Transport Option".needTranslation); + + notifyListeners(); + var response = + await emergencyServicesRepo.getTransportationMethods(id: 4767477); + + response.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + }, + (apiResponse) { + transportationOptions = apiResponse.data!; + LoaderBottomSheet.hideLoader(); + notifyListeners(); + }, + ); + } + + void setTransportationOption(PatientERTransportationMethod item) { + selectedTransportOption = item; + } + + void updateCallingPlace(AmbulanceCallingPlace? value) { + callingPlace = value!; + notifyListeners(); + } + + void updateDirection(AmbulanceDirection? value) { + ambulanceDirection = value!; + notifyListeners(); + } + + void placeValueInController() { + if (isGMSAvailable) { + gmsController = Completer(); + } else { + hmsController = Completer(); + } + } + + void moveController(Location location) { + if (isGMSAvailable) { + gmsController?.future.then((controller) { + controller.animateCamera( + GMSMapServices.CameraUpdate.newCameraPosition( + GMSMapServices.CameraPosition( + target: GMSMapServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } else { + hmsController?.future.then((controller) { + controller.animateCamera( + HMSCameraServices.CameraUpdate.newCameraPosition( + HMSCameraServices.CameraPosition( + target: HMSCameraServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } + } + + void moveToCurrentLocation() { + moveController(Location(lat: appState.userLat, lng: appState.userLong)); + } + FutureOr getProjects() async { + // if (hospitalList.isNotEmpty) return; + var response = await emergencyServicesRepo.getProjectList(); + + response.fold( + (failure) async {}, + (apiResponse) async { + List? data = apiResponse.data; + if (data == null) return; + hospitalList = data; + hmgHospitalList = data.where((e) => e.isHMC == false).toList(); + hmcHospitalList = data.where((e) => e.isHMC == true).toList(); + hmgCount = hmgHospitalList?.length ?? 0; + hmcCount = hmcHospitalList?.length ?? 0; + notifyListeners(); + }, + ); + } + + setSelectedFacility(FacilitySelection selection) { + selectedFacility = selection; notifyListeners(); + } + searchHospitals(String query) { + if (query.isEmpty) { + getDisplayList(); + return; + } + List? sourceList; + switch (selectedFacility) { + case FacilitySelection.ALL: + sourceList = hospitalList; + break; + case FacilitySelection.HMG: + sourceList = hmgHospitalList; + break; + case FacilitySelection.HMC: + sourceList = hmcHospitalList; + break; + } + displayList = sourceList + ?.where((hospital) => + hospital.name != null && + hospital.name!.toLowerCase().contains(query.toLowerCase())) + .toList(); + notifyListeners(); + } + + getDisplayList() { + switch (selectedFacility) { + case FacilitySelection.ALL: + displayList = hospitalList; + break; + case FacilitySelection.HMG: + displayList = hmgHospitalList; + break; + case FacilitySelection.HMC: + displayList = hmcHospitalList; + break; + } + notifyListeners(); + } + + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + String? getSelectedHospitalName() { + return selectedHospital?.getName(isArabic); + } + + void updateThePickupPlaceFromLocation(bool value) { + pickupFromInsideTheLocation = value; + notifyListeners(); + } + + void flushData() { + selectedHospital = null; + displayList = hospitalList; + selectedFacility = FacilitySelection.ALL; + pickupFromInsideTheLocation = false; + notifyListeners(); + } + + void flushPickupInformation() { + ambulanceDirection = AmbulanceDirection.ONE_WAY; + callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; + notifyListeners(); + } + + num getTotalPrice() { + print("the ambulance direction is $ambulanceDirection"); + switch (ambulanceDirection) { + case AmbulanceDirection.ONE_WAY: + return selectedTransportOption?.priceTotal ?? 0; + case AmbulanceDirection.TWO_WAY: + return (selectedTransportOption?.priceTotal ?? 0) * 2; + } + } + + Future updateAppointment(bool value) async { + haveAnAppointment = value; + if (value) { + await appointmentRepo.getPatientAppointments( + isActiveAppointment: true, isArrivedAppointments: false); + } + notifyListeners(); } } diff --git a/lib/features/emergency_services/models/AmbulanceCallingPlace.dart b/lib/features/emergency_services/models/AmbulanceCallingPlace.dart new file mode 100644 index 00000000..cf648f7c --- /dev/null +++ b/lib/features/emergency_services/models/AmbulanceCallingPlace.dart @@ -0,0 +1,4 @@ +enum AmbulanceCallingPlace{ + FROM_HOSPITAL, + TO_HOSPITAL +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/ambulance_direction.dart b/lib/features/emergency_services/models/ambulance_direction.dart new file mode 100644 index 00000000..d3019214 --- /dev/null +++ b/lib/features/emergency_services/models/ambulance_direction.dart @@ -0,0 +1,3 @@ +enum AmbulanceDirection{ + ONE_WAY, TWO_WAY +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/request_model/PatientER_RC.dart b/lib/features/emergency_services/models/request_model/PatientER_RC.dart new file mode 100644 index 00000000..8cb3876d --- /dev/null +++ b/lib/features/emergency_services/models/request_model/PatientER_RC.dart @@ -0,0 +1,205 @@ + +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart' show PatientERTransportationMethod; + +class PatientER_RC { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + bool? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + int? orderServiceID; + String? patientIdentificationID; + dynamic patientOutSa; + int? projectID; + int? lineItemNo; + TransportationDetails? transportationDetails; + PatientERTransportationMethod? patientERTransportationMethod; + + PatientER_RC( + {this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType, + this.orderServiceID, + this.patientIdentificationID, + this.patientOutSa, + this.projectID, + this.lineItemNo, + this.transportationDetails}); + + PatientER_RC.fromJson(Map json) { + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + orderServiceID = json['OrderServiceID']; + patientIdentificationID = json['PatientIdentificationID']; + patientOutSa = json['patientOutSa']; + projectID = json['projectID']; + lineItemNo = json['lineItemNo']; + transportationDetails = json['transportationDetails'] != null + ? new TransportationDetails.fromJson(json['transportationDetails']) + : null; + } + + Map toJson() { + final Map data = new Map(); + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientID'] = this.patientID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + data['OrderServiceID'] = this.orderServiceID; + data['PatientIdentificationID'] = this.patientIdentificationID; + data['patientOutSa'] = this.patientOutSa; + data['projectID'] = this.projectID; + data['lineItemNo'] = this.lineItemNo; + if (this.transportationDetails != null) { + data['transportationDetails'] = this.transportationDetails!.toJson(); + } + return data; + } +} + +class TransportationDetails { + int? direction; + int? haveAppointment; + int? tripType; + int? pickupUrgency; + int? pickupSpot; + String? pickupDateTime; + String? transportationType; + int? ambulate; + String? ambulateTitle; + String? notes; + int? requesterFileNo; + String? requesterMobileNo; + bool? requesterIsOutSA; + String? pickupLocationName; + String? dropoffLocationName; + String? pickupLatitude; + String? pickupLongitude; + String? dropoffLatitude; + String? dropoffLongitude; + String? appointmentNo; + String? appointmentClinicName; + String? appointmentDoctorName; + String? appointmentBranch; + String? appointmentTime; + + TransportationDetails( + {this.direction, + this.haveAppointment, + this.tripType, + this.pickupUrgency, + this.pickupSpot, + this.pickupDateTime, + this.transportationType, + this.ambulate, + this.ambulateTitle, + this.notes, + this.requesterFileNo, + this.requesterMobileNo, + this.requesterIsOutSA, + this.pickupLocationName, + this.dropoffLocationName, + this.pickupLatitude, + this.pickupLongitude, + this.dropoffLatitude, + this.dropoffLongitude, + this.appointmentNo, + this.appointmentClinicName, + this.appointmentDoctorName, + this.appointmentBranch, + this.appointmentTime}); + + TransportationDetails.fromJson(Map json) { + direction = json['direction']; + haveAppointment = json['haveAppointment']; + tripType = json['tripType']; + pickupUrgency = json['pickupUrgency']; + pickupSpot = json['pickupSpot']; + pickupDateTime = json['pickupDateTime']; + transportationType = json['transportationType']; + ambulate = json['ambulate']; + ambulateTitle = ""; + notes = json['notes']; + requesterFileNo = json['requesterFileNo']; + requesterMobileNo = json['requesterMobileNo']; + requesterIsOutSA = json['requesterIsOutSA']; + pickupLocationName = json['pickupLocationName']; + dropoffLocationName = json['dropoffLocationName']; + pickupLatitude = json['pickup_Latitude']; + pickupLongitude = json['pickup_Longitude']; + dropoffLatitude = json['dropoff_Latitude']; + dropoffLongitude = json['dropoff_Longitude']; + appointmentNo = json['appointmentNo']; + appointmentClinicName = json['appointmentClinicName']; + appointmentDoctorName = json['appointmentDoctorName']; + appointmentBranch = json['appointmentBranch']; + appointmentTime = json['appointmentTime']; + } + + Map toJson() { + final Map data = new Map(); + data['direction'] = this.direction; + data['haveAppointment'] = this.haveAppointment; + data['tripType'] = this.tripType; + data['pickupUrgency'] = this.pickupUrgency; + data['pickupSpot'] = this.pickupSpot; + data['pickupDateTime'] = this.pickupDateTime; + data['transportationType'] = this.transportationType; + data['ambulate'] = this.ambulate; + data['ambulateTitle'] = this.ambulateTitle; + data['notes'] = this.notes; + data['requesterFileNo'] = this.requesterFileNo; + data['requesterMobileNo'] = this.requesterMobileNo; + data['requesterIsOutSA'] = this.requesterIsOutSA; + data['pickupLocationName'] = this.pickupLocationName; + data['dropoffLocationName'] = this.dropoffLocationName; + data['pickup_Latitude'] = this.pickupLatitude; + data['pickup_Longitude'] = this.pickupLongitude; + data['dropoff_Latitude'] = this.dropoffLatitude; + data['dropoff_Longitude'] = this.dropoffLongitude; + data['appointmentNo'] = this.appointmentNo; + data['appointmentClinicName'] = this.appointmentClinicName; + data['appointmentDoctorName'] = this.appointmentDoctorName; + data['appointmentBranch'] = this.appointmentBranch; + data['appointmentTime'] = this.appointmentTime; + return data; + } +} diff --git a/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart b/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart new file mode 100644 index 00000000..e4e6c7d7 --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart @@ -0,0 +1,56 @@ +class PatientERTransportationMethod { + int? iD; + String? serviceID; + int? orderServiceID; + String? text; + String? textN; + dynamic price; + dynamic priceVAT; + dynamic priceTotal; + bool? isEnabled; + int? orderId; + int? quantity; + + PatientERTransportationMethod( + {this.iD, + this.serviceID, + this.orderServiceID, + this.text, + this.textN, + this.price, + this.priceVAT, + this.priceTotal, + this.isEnabled, + this.orderId, + this.quantity}); + + PatientERTransportationMethod.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + orderServiceID = json['OrderServiceID']; + text = json['Text']; + textN = json['TextN']; + price = json['Price']; + priceVAT = json['PriceVAT']; + priceTotal = json['PriceTotal']; + isEnabled = json['IsEnabled']; + orderId = json['OrderId']; + quantity = json['Quantity']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['OrderServiceID'] = this.orderServiceID; + data['Text'] = this.text; + data['TextN'] = this.textN; + data['Price'] = this.price; + data['PriceVAT'] = this.priceVAT; + data['PriceTotal'] = this.priceTotal; + data['IsEnabled'] = this.isEnabled; + data['OrderId'] = this.orderId; + data['Quantity'] = this.quantity; + return data; + } +} \ No newline at end of file diff --git a/lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart b/lib/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart similarity index 100% rename from lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart rename to lib/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart diff --git a/lib/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart b/lib/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart similarity index 100% rename from lib/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart rename to lib/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart diff --git a/lib/features/location/GeocodeResponse.dart b/lib/features/location/GeocodeResponse.dart new file mode 100644 index 00000000..3e911aa3 --- /dev/null +++ b/lib/features/location/GeocodeResponse.dart @@ -0,0 +1,80 @@ +class GeocodeResponse { + final List results; + final String status; + + GeocodeResponse({ + required this.results, + required this.status, + }); + + factory GeocodeResponse.fromJson(Map json) { + final resultsList = (json['results'] as List? ?? []) + .map((e) => GeocodeResult.fromJson(e as Map)) + .toList(); + + return GeocodeResponse( + results: resultsList, + status: json['status'] ?? '', + ); + } + @override + String toString() { + return 'GeocodeResponse(status: $status, results: [${results.map((r) => r.toString()).join(', ')}])'; + } +} + +class GeocodeResult { + final String formattedAddress; + final Geometry geometry; + final String placeId; + + GeocodeResult({ + required this.formattedAddress, + required this.geometry, + required this.placeId, + }); + + factory GeocodeResult.fromJson(Map json) { + return GeocodeResult( + formattedAddress: json['formatted_address'] ?? '', + geometry: Geometry.fromJson(json['geometry']), + placeId: json['place_id'] ?? '', + ); + } + @override + String toString() { + return 'GeocodeResult(formattedAddress: $formattedAddress, placeId: $placeId, geometry: ${geometry.toString()})'; + } +} + +class Geometry { + final Location location; + + Geometry({required this.location}); + + factory Geometry.fromJson(Map json) { + return Geometry( + location: Location.fromJson(json['location']), + ); + } + + @override + String toString() => 'Geometry(location: ${location.toString()})'; +} + +class Location { + final double lat; + final double lng; + + Location({required this.lat, required this.lng}); + + factory Location.fromJson(Map json) { + return Location( + lat: (json['lat'] as num).toDouble(), + lng: (json['lng'] as num).toDouble(), + ); + } + + @override + String toString() => 'Location(lat: $lat, lng: $lng)'; +} \ No newline at end of file diff --git a/lib/features/location/PlaceDetails.dart b/lib/features/location/PlaceDetails.dart new file mode 100644 index 00000000..82846ea0 --- /dev/null +++ b/lib/features/location/PlaceDetails.dart @@ -0,0 +1,14 @@ +class PlaceDetails { + final double lat; + final double lng; + + PlaceDetails({required this.lat, required this.lng}); + + factory PlaceDetails.fromJson(Map json) { + final loc = json['result']['geometry']['location']; + return PlaceDetails( + lat: (loc['lat'] as num).toDouble(), + lng: (loc['lng'] as num).toDouble(), + ); + } +} \ No newline at end of file diff --git a/lib/features/location/PlacePrediction.dart b/lib/features/location/PlacePrediction.dart new file mode 100644 index 00000000..ea681538 --- /dev/null +++ b/lib/features/location/PlacePrediction.dart @@ -0,0 +1,11 @@ +class PlacePrediction { + final String description; + final String placeID; + PlacePrediction({required this.description, required this.placeID}); + factory PlacePrediction.fromJson(Map json) { + return PlacePrediction( + description: json['description'] ?? '', + placeID: json['place_id'] ?? '', + ); + } +} \ No newline at end of file diff --git a/lib/features/location/location_repo.dart b/lib/features/location/location_repo.dart new file mode 100644 index 00000000..46c97ac3 --- /dev/null +++ b/lib/features/location/location_repo.dart @@ -0,0 +1,153 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; + +import '../../core/api_consts.dart'; +import 'PlacePrediction.dart'; + +abstract class LocationRepo { + Future>>> + getPlacePredictionsAsInput(String input); + + Future>> + getPlaceDetailsOfSelectedPrediction(String placeId); + + Future>> + getGeoCodeFromLatLng(double lat, double lng); +} + +class LocationRepoImpl implements LocationRepo { + final ApiClient apiClient; + + LocationRepoImpl({required this.apiClient}); + + @override + Future>>> getPlacePredictionsAsInput( + String input) async { + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input®ion=SA&key=$GOOGLE_API_KEY', + ); + + GenericApiModel>? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['predictions']; + final predictionsList = list + .map((item) => + PlacePrediction.fromJson(item as Map)) + .toList() + .cast(); + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getGeoCodeFromLatLng(double lat, double lng) async { + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lng&key=$GOOGLE_API_KEY', + ); + + + GenericApiModel? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final predictionsList = GeocodeResponse.fromJson(response); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getPlaceDetailsOfSelectedPrediction(String placeId) async { + + + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/details/json' + '?place_id=$placeId&fields=geometry&key=$GOOGLE_API_KEY', + ); + + GenericApiModel? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final predictionsList = PlaceDetails.fromJson(response); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/location/location_view_model.dart b/lib/features/location/location_view_model.dart new file mode 100644 index 00000000..e7cd6853 --- /dev/null +++ b/lib/features/location/location_view_model.dart @@ -0,0 +1,120 @@ +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:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +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'; + +class LocationViewModel extends ChangeNotifier { + final LocationRepo locationRepo; + final ErrorHandlerService errorHandlerService; + + LocationViewModel({required this.locationRepo, required this.errorHandlerService}); + + List predictions = []; + PlacePrediction? selectedPrediction; + bool isPredictionLoading = false; + GeocodeResponse? geocodeResponse; + PlaceDetails? placeDetails; + + Location? mapCapturedLocation; + + + + + + FutureOr getPlacesPrediction(String input) async { + predictions = []; + isPredictionLoading= true; + final result = await locationRepo.getPlacePredictionsAsInput(input); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + predictions = apiModel.data??[]; + }, + ); + isPredictionLoading = false; + notifyListeners(); + } + + FutureOr getPlaceEncodedData(double? lat, double? lng) async { + geocodeResponse = null; + final result = await locationRepo.getGeoCodeFromLatLng(lat!, lng!); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + print("Geocode Response: ${apiModel.data}"); + geocodeResponse = apiModel.data; + }, + ); + notifyListeners(); + } + + FutureOr getPlaceDetails(String placeID) async { + placeDetails = null; + final result = await locationRepo.getPlaceDetailsOfSelectedPrediction(placeID); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + placeDetails = apiModel.data; + }, + ); + notifyListeners(); + } + + 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) { + getPlaceEncodedData(mapCapturedLocation!.lat, mapCapturedLocation!.lng); + } + } + + void updateSearchQuery(String? value) { + if(value == null || value.isEmpty){ + predictions = []; + return; + } + + getPlacesPrediction(value); + } + + void flushSearchPredictions() { + predictions = []; + mapCapturedLocation= null; + placeDetails= null; + geocodeResponse= null; + selectedPrediction= null; + + notifyListeners(); + } + + FutureOr selectPlacePrediction(PlacePrediction placePrediction) async{ + selectedPrediction= placePrediction; + await getPlaceDetails(placePrediction.placeID); + } + + +} \ No newline at end of file diff --git a/lib/features/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart index 342fcea6..9a211d04 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -101,4 +101,6 @@ class HospitalsModel { data['UsingInDoctorApp'] = this.usingInDoctorApp; return data; } + + } diff --git a/lib/main.dart b/lib/main.dart index 259ce3b5..6426244b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -129,6 +130,8 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ),ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart index 31dc74ec..65d779e8 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart @@ -72,6 +72,7 @@ class HospitalListItem extends StatelessWidget { ); Widget get distanceInfo => Row( + spacing: 4.w, children: [ Visibility( visible: (hospitalData?.distanceInKMs != "0"), @@ -91,9 +92,9 @@ class HospitalListItem extends StatelessWidget { labelText: "Distance not available".needTranslation, textColor: AppColors.blackColor, ), - SizedBox( - width: 8.h, - ) + // SizedBox( + // width: 8.h, + // ) ], )), Visibility( diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index eb36f92f..f39aaf29 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -8,10 +8,14 @@ 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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/requesting_services_page.dart' show RequestingServicesPage; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.dart' show TrackingScreen; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/CustomSwitch.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -30,13 +34,37 @@ class CallAmbulancePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - bottomSheet: ExpandableBottomSheet( - bottomSheetType: - context.watch().bottomSheetType, - children: { - BottomSheetType.EXPANDED: ExpanedBottomSheet(context), - BottomSheetType.FIXED: FixedBottomSheet(context), - }, + floatingActionButton: Visibility( + visible: context.watch().bottomSheetType == + BottomSheetType.FIXED, + child: Padding( + padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.locate_me, width: 24.h, height: 24.h) + .paddingAll(12.h) + .onPress(() { + context + .read() + .moveToCurrentLocation(); + }), + ), + ), + ), + bottomSheet: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ExpandableBottomSheet( + bottomSheetType: + context.watch().bottomSheetType, + children: { + BottomSheetType.EXPANDED: ExpanedBottomSheet(context), + BottomSheetType.FIXED: FixedBottomSheet(context), + }, + ), + ], ), body: Stack( children: [ @@ -46,22 +74,40 @@ class CallAmbulancePage extends StatelessWidget { currentLocation: context.read().getGMSLocation(), onCameraMoved: (value) => context - .read() - .handleGMSMapCameraMoved(value)) + .read() + .handleGMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: true, + inputController: + context.read().gmsController, + showCenterMarker: true, + ) else HMSMap( currentLocation: context.read().getHMSLocation(), onCameraMoved: (value) => context - .read() - .handleHMSMapCameraMoved(value)), + .read() + .handleHMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: false, + inputController: + context.read().hmsController, + showCenterMarker: true, + ), Align( alignment: AlignmentDirectional.topStart, child: Utils.buildSvgWithAssets( - icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h), - ).paddingOnly(top: 51.h, left: 24.h).onPress((){ - Navigator.pop(context); - }) + icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) + .onPress(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + }), + ).paddingOnly(top: 51.h, left: 24.h), ], ), ); @@ -70,7 +116,6 @@ class CallAmbulancePage extends StatelessWidget { Widget FixedBottomSheet(BuildContext context) { return GestureDetector( onVerticalDragUpdate: (details){ - print("the delta is ${details.delta.dy}"); if(details.delta.dy<0){ @@ -81,22 +126,24 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - - padding: EdgeInsetsDirectional.only(end:24.h, bottom: 24.h), - child: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 12.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.locate_me, width: 24.h, height: 24.h) - .paddingAll(12.h), - ), - ), - ], - ), + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // Padding( + // + // padding: EdgeInsetsDirectional.only(end:24.h, bottom: 24.h), + // child: DecoratedBox( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, borderRadius: 12.h), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.locate_me, width: 24.h, height: 24.h) + // .paddingAll(12.h).onPress((){ + // context.read().moveToCurrentLocation(); + // }), + // ), + // ), + // ], + // ), // PositionedDirectional( // end: 1, // child: Padding( @@ -116,25 +163,7 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: 24.h, children: [ - TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, - leadingIcon: AppAssets.location_pickup, - isAllowLeadingIcon: true, - isEnable: false, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - padding: EdgeInsets.symmetric( - vertical: ResponsiveExtension(10).h, - horizontal: ResponsiveExtension(15).h, - ), - ).onPress(() { - context - .read() - .updateBottomSheetState(BottomSheetType.EXPANDED); - }).paddingOnly(right: 24.h, left: 24.h), + inputFields(context), SizedBox( height: 200.h, child: DecoratedBox( @@ -152,11 +181,11 @@ class CallAmbulancePage extends StatelessWidget { Column( spacing: 4.h, children: [ - "Select Pickup Location".needTranslation.toText21( + "Select Pickup Details".needTranslation.toText21( weight: FontWeight.w600, color: AppColors.textColor, ), - " Please select the location of pickup" + " Please select the details of pickup" .needTranslation .toText12( fontWeight: FontWeight.w500, @@ -165,7 +194,7 @@ class CallAmbulancePage extends StatelessWidget { ], ), CustomButton( - text: "Select Location".needTranslation, + text: "Select Details".needTranslation, onPressed: () { context .read() @@ -207,11 +236,11 @@ class CallAmbulancePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 16.h, children: [ - "Select Pickup Location".needTranslation.toText21( - weight: FontWeight.w600, - color: AppColors.textColor, - ), - locationsSections(context), + // "Select Pickup Location".needTranslation.toText21( + // weight: FontWeight.w600, + // color: AppColors.textColor, + // ), + // locationsSections(context), hospitalAndPickUpSection(context), ], @@ -281,44 +310,72 @@ class CallAmbulancePage extends StatelessWidget { return DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, + borderRadius: 24.r, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, children: [ + // Row( + // children: [ + // hospitalAndPickUpItemContent( + // title: "Select Hospital".needTranslation, + // subTitle: "hospital".needTranslation, + // leadingIcon: AppAssets.hospital, + // ), + // Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, + // width: 24.h, height: 24.h) + // .paddingAll(16.h) + // ], + // ).onPress((){ + // showHospitalBottomSheet(context); + // }), + // SizedBox(height: 16.h), + // Divider( + // color: AppColors.bottomNAVBorder, + // height: 1, + // thickness: 1, + // ), + // SizedBox(height: 16.h), + Row( children: [ hospitalAndPickUpItemContent( - title: "Select Hospital".needTranslation, - subTitle: "hospital".needTranslation, - leadingIcon: AppAssets.hospital, + title: "Pick".needTranslation, + subTitle: "Inside the home".needTranslation, + leadingIcon: AppAssets.pickup_bed, ), - Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, - width: 24.h, height: 24.h) - .paddingAll(16.h) + CustomSwitch( + value: context + .watch() + .pickupFromInsideTheLocation, + onChanged: (value){ + context + .read() + .updateThePickupPlaceFromLocation(value); + }, + ) ], - ).onPress((){ - showHospitalBottomSheet(context); - }), - SizedBox(height: 16.h), - Divider( - color: AppColors.bottomNAVBorder, - height: 1, - thickness: 1, ), - SizedBox(height: 16.h), Row( children: [ hospitalAndPickUpItemContent( - title: "Pick".needTranslation, - subTitle: "Inside the home".needTranslation, - leadingIcon: AppAssets.pickup_bed, + title: '', + subTitle: "Have any appointment".needTranslation, + leadingIcon: AppAssets.appointment_checkin_icon, ), CustomSwitch( - value: context.watch().isGMSAvailable, - onChanged: (value){ - context.read().setIsGMSAvailable( value); + value: context + .watch() + .haveAnAppointment, + onChanged: (value) { + if (value) { + openAppointmentList(context); + } + context + .read() + .updateAppointment(value); }, ) ], @@ -354,13 +411,22 @@ class CallAmbulancePage extends StatelessWidget { this.leadingIcon(leadingIcon), Expanded( child: Column( + mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - title.toText12( - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + Visibility( + visible: title.isNotEmpty, + child: Column( + children: [ + title.toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 2.h), + ], + ), ), - SizedBox(height: 2.h), subTitle.toText14( color: AppColors.textColor, weight: FontWeight.w500, @@ -418,15 +484,24 @@ class CallAmbulancePage extends StatelessWidget { ], ), ), - "\$250".toText24( - fontWeight: FontWeight.w600, - color: AppColors.textColor, - ) - ], - ), - CustomButton( - text: "Submit Request".needTranslation, - onPressed: () { + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context + .read() + .getTotalPrice() ?? + 0)) + .toText24( + fontWeight: FontWeight.w600, + color: AppColors.textColor, + letterSpacing: -2), + AppColors.blackColor, + 17.h) + + // Utils.getPaymentAmountWithSymbol2(context.read().selectedTransportOption?.priceTotal??"0", letterSpacing: -2) + ], + ), + CustomButton( + text: "Submit Request".needTranslation, + onPressed: () { Navigator.push(context, CustomPageRoute(page: RequestingServicesPage())); }) ], @@ -441,7 +516,26 @@ class CallAmbulancePage extends StatelessWidget { title: LocaleKeys.selectHospital.tr(), context, - child: HospitalBottomSheetBody(), + child: Consumer( + builder:(_,vm,__)=> HospitalBottomSheetBody( + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: + vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), isFullScreen: false, isCloseButtonVisible: true, hasBottomPadding: false, @@ -449,4 +543,124 @@ class CallAmbulancePage extends StatelessWidget { callBackFunc: () {}, ); } + + ///it will show the places field first and then hospital field + PlaceFirstThanHospitalField(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + textPlaceInput(context), + hospitalField(context), + ], + ).paddingOnly(right: 24.h, left: 24.h); + } + + HospitalFieldFirstThanPlaces(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [hospitalField(context), textPlaceInput(context)], + ).paddingOnly(right: 24.h, left: 24.h); + } + + textPlaceInput(context) { + return Consumer(builder: (_, vm, __) { + print( + "the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + return SizedBox( + width: MediaQuery.sizeOf(context).width, + child: TextInputWidget( + labelText: "Enter Pickup Location Manually".needTranslation, + hintText: "Enter Pickup Location".needTranslation, + controller: TextEditingController( + text: vm.geocodeResponse?.results.first.formattedAddress ?? + vm.selectedPrediction?.description, + ), + leadingIcon: AppAssets.location_pickup, + isAllowLeadingIcon: true, + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ).onPress(() { + openLocationInputBottomSheet(context); + }), + ); + }); + } + + ///decide which field to show first based on the selected calling place + inputFields(BuildContext context) { + return context.read().callingPlace == + AmbulanceCallingPlace.FROM_HOSPITAL + ? HospitalFieldFirstThanPlaces(context) + : PlaceFirstThanHospitalField(context); + } + + openLocationInputBottomSheet(BuildContext context) { + context.read().flushSearchPredictions(); + showCommonBottomSheetWithoutHeight( + title: "".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .8, + child: LocationInputBottomSheet(), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } + + hospitalField(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + hospitalAndPickUpItemContent( + title: "Select Hospital".needTranslation, + subTitle: context + .read() + .getSelectedHospitalName() ?? + "hospital".needTranslation, + leadingIcon: AppAssets.hospital, + ), + Utils.buildSvgWithAssets( + icon: AppAssets.down_cheveron, width: 24.h, height: 24.h) + .paddingAll(16.h) + ], + ).onPress(() { + print("the item is clicked"); + showHospitalBottomSheet(context); + }).paddingSymmetrical( + 10.w, + 12.h, + ), + ); + } + + void openAppointmentList(BuildContext context) { + // showCommonBottomSheetWithoutHeight( + // title: "".needTranslation, + // context, + // child: SizedBox( + // height: MediaQuery.sizeOf(context).height * .6, + // child: AppointmentBottomSheet(), + // ), + // isFullScreen: false, + // isCloseButtonVisible: true, + // hasBottomPadding: false, + // backgroundColor: AppColors.bottomSheetBgColor, + // callBackFunc: () {}, + // ); + } } diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index 9df05586..c0f31e16 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -203,11 +203,17 @@ class TrackingScreen extends StatelessWidget { if (context.read().isGMSAvailable || Platform.isIOS) { return GMSMap( myLocationEnabled: false, + onCameraIdle: (){ + + }, currentLocation: context.read().getGMSLocation(), onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value)); } else { return HMSMap( myLocationEnabled: false, + onCameraIdle: (){ + + }, currentLocation: context.read().getHMSLocation(), onCameraMoved: (value) => context.read().handleHMSMapCameraMoved(value)); } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart index c594888f..14ea705e 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart @@ -2,12 +2,17 @@ import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; @@ -20,8 +25,20 @@ import 'package:provider/provider.dart'; class HospitalBottomSheetBody extends StatelessWidget { final TextEditingController searchText = TextEditingController(); + final Debouncer debouncer = Debouncer(milliseconds: 500); - HospitalBottomSheetBody({super.key}); + + final int hmcCount; + final int hmgCount; + final List? displayList; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onFacilityClicked; + final Function(HospitalsModel) onHospitalClicked; + final Function(String) onHospitalSearch; + + + + HospitalBottomSheetBody({super.key, required this.hmcCount, required this.hmgCount, this.displayList, required this.selectedFacility, required this.onFacilityClicked, required this.onHospitalClicked, required this.onHospitalSearch}); @override Widget build(BuildContext context) { @@ -29,13 +46,14 @@ class HospitalBottomSheetBody extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextInputWidget( labelText: LocaleKeys.search.tr(), hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - + debouncer.run((){ + onHospitalSearch(value??""); + }); }, isEnable: true, prefix: null, @@ -51,10 +69,11 @@ class HospitalBottomSheetBody extends StatelessWidget { ), SizedBox(height: 24.h), TypeSelectionWidget( - hmcCount: "0", - hmgCount: "0", + selectedFacility:selectedFacility , + hmcCount: hmcCount.toString(), + hmgCount: hmgCount.toString(), onitemClicked: (selectedValue){ - + onFacilityClicked(selectedValue); }, ), SizedBox(height: 21.h), @@ -63,17 +82,17 @@ class HospitalBottomSheetBody extends StatelessWidget { child: ListView.separated( itemBuilder: (_, index) { - var hospital = null; + var hospital = displayList?[index]; return HospitalListItem( - hospitalData: hospital, - isLocationEnabled: false, + hospitalData: Utils.convertToPatientDoctorAppointmentList(hospital), + isLocationEnabled: true, ).onPress(() { - + onHospitalClicked(hospital!); });}, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), - itemCount: 0, + itemCount: displayList?.length ?? 0, )) ], ); diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart b/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart index 564bd640..de20d1aa 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart' show AppAssets; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart' show TransportOptionItem; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; class AmbulanceOptionSelectionBottomSheet extends StatelessWidget { final Function onTap; @@ -12,40 +16,36 @@ class AmbulanceOptionSelectionBottomSheet extends StatelessWidget { const AmbulanceOptionSelectionBottomSheet({super.key, required this.onTap}); @override Widget build(BuildContext context) { - return Column( - spacing: 16.h, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return Selector>( + selector: (_, model) => model.transportationOptions, + builder: (_, data, __) => Column( + spacing: 16.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView.separated( + separatorBuilder: (_,__)=> SizedBox( + height: 16.h, + ), + itemCount: data.length??0, + itemBuilder: (_, index) => TransportOptionItem( + title: Utils.getTextWRTCurrentLanguage(data[index].text, data[index].textN), + subTitle: "", + firstIcon: AppAssets.location_pickup, + middleIcon: AppAssets.to_arrow, + lastIcon: AppAssets.hospital, + price: data[index].priceTotal.toString(), + onTap: () { + onTap(); + context.read().setTransportationOption(data[index]); + }, + ), + ), + ), - TransportOptionItem( - title: "Home to Hospital (One Way)", - subTitle: "Pickup from home location to hospital", - firstIcon: AppAssets.location_pickup, - middleIcon:AppAssets.to_arrow, - lastIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - TransportOptionItem( - title: "Hospital to Home (One Way)", - subTitle: "Pickup from hospital to home", - lastIcon: AppAssets.location_pickup, - middleIcon:AppAssets.to_arrow, - firstIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - TransportOptionItem( - title: "Home to Hospital (Two Way)", - subTitle: "Pick from home to hospital then drop off to home", - firstIcon: AppAssets.location_pickup, - middleIcon:AppAssets.dual_arrow, - lastIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - SizedBox(height: 16.h,) - ], + ], + ), ); } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart b/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart new file mode 100644 index 00000000..8616fc33 --- /dev/null +++ b/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart @@ -0,0 +1,92 @@ +// import 'package:flutter/cupertino.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +// import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +// import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +// import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +// import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart'; +// import 'package:hmg_patient_app_new/theme/colors.dart'; +// import 'package:provider/provider.dart'; +// +// class AppointmentBottomSheet extends StatelessWidget{ +// @override +// Widget build(BuildContext context) { +// // TODO: implement build +// throw UnimplementedError(); +// } +// +// Widget getAppointList(EmergencyServicesViewModel viewmodel, List filteredAppointmentList) { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// ListView.separated( +// padding: EdgeInsets.only(top: 24.h), +// shrinkWrap: true, +// physics: NeverScrollableScrollPhysics(), +// itemCount: viewmodel.isMyAppointmentsLoading +// ? 5 +// : filteredAppointmentList.isNotEmpty +// ? filteredAppointmentList.length +// : 1, +// itemBuilder: (context, index) { +// return viewmodel.isMyAppointmentsLoading +// ? Container( +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), +// child: DoctorCard(doctorsListResponseModel: doctorsListResponseModel, isLoading: viewmodel.isMyAppointmentsLoading, bookAppointmentsViewModel: bookAppointmentsViewModel), +// ).paddingSymmetrical(24.h, 0.h) +// : filteredAppointmentList.isNotEmpty +// ? 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: AppointmentCard( +// patientAppointmentHistoryResponseModel: filteredAppointmentList[index], +// myAppointmentsViewModel: myAppointmentsViewModel, +// bookAppointmentsViewModel: bookAppointmentsViewModel, +// isLoading: false, +// isFromHomePage: false, +// ), +// ).paddingSymmetrical(24.h, 0.h), +// ), +// ), +// ) +// : Utils.getNoDataWidget( +// context, +// noDataText: "You don't have any appointments yet.".needTranslation, +// callToActionButton: CustomButton( +// text: LocaleKeys.bookAppo.tr(context: context), +// onPressed: () { +// Navigator.of(context).push( +// CustomPageRoute( +// page: BookAppointmentPage(), +// ), +// ); +// }, +// backgroundColor: Color(0xffFEE9EA), +// borderColor: Color(0xffFEE9EA), +// textColor: Color(0xffED1C2B), +// fontSize: 14.f, +// fontWeight: FontWeight.w500, +// borderRadius: 12.r, +// padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), +// height: 40.h, +// icon: AppAssets.add_icon, +// iconColor: AppColors.primaryRedColor, +// ).paddingSymmetrical(48.h, 0.h), +// ); +// }, +// separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), +// ), +// SizedBox(height: 24.h), +// ], +// ); +// } +// +// +// } \ No newline at end of file diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart new file mode 100644 index 00000000..5d073d4b --- /dev/null +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -0,0 +1,172 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/ambulance_direction.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + +import '../../../../generated/locale_keys.g.dart' show LocaleKeys; + +class PickupLocation extends StatelessWidget { + final VoidCallback onTap; + const PickupLocation({super.key, required this.onTap}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Select Pickup Direction" + .needTranslation + .toText24(color: AppColors.textColor,isBold: true), + SizedBox( + height: 16.h, + ), + "Select Direction" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox( + height: 12.h, + ), + Selector( + selector: (context, viewModel) => viewModel.callingPlace, + builder: (context, value, _) { + return Column( + spacing: 12.h, + children: [ + RadioGroup( + groupValue: value, + onChanged: (value) { + context + .read() + .updateCallingPlace(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.TO_HOSPITAL, + groupValue: value, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "To Hospital" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL); + }), + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.FROM_HOSPITAL, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "From Hospital" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL); + }), + ], + ), + ), + Visibility( + visible: value == AmbulanceCallingPlace.TO_HOSPITAL, + child: Selector( + selector: (context, viewModel) => + viewModel.ambulanceDirection, + builder: (context, directionValue, _) { + return Column( + spacing: 12.h, + children: [ + "Select Way" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w600), + RadioGroup( + groupValue: directionValue, + onChanged: (value) { + context + .read() + .updateDirection(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceDirection.ONE_WAY, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "One Way" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.ONE_WAY); + }), + Row( + children: [ + Radio( + value: AmbulanceDirection.TWO_WAY, + // activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "Two Way" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.TWO_WAY); + }), + ], + ), + ), + ], + ); + }), + ) + ], + ); + }), + SizedBox( + height: 16.h, + ), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: onTap, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + iconColor: AppColors.whiteColor, + ), + ], + ); + } +} diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart index 25118241..9a959ebb 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart @@ -48,7 +48,6 @@ class TransportOptionItem extends StatelessWidget { titleSection() { return Row( - children: [ Expanded( child: Column( @@ -56,8 +55,8 @@ class TransportOptionItem extends StatelessWidget { children: [ title.toText16( color: AppColors.textColor, weight: FontWeight.w600), - subTitle.toText12( - color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + // subTitle.toText12( + // color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -69,18 +68,23 @@ class TransportOptionItem extends StatelessWidget { headerSection() { return Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - Expanded( - child: Row( - children: [ - buildIcon(firstIcon), - Utils.buildSvgWithAssets( - icon: middleIcon, width: 24.h, height: 24.h).paddingAll(8.h), - buildIcon(lastIcon) - ], - ), + // Expanded( + // child: Row( + // children: [ + // buildIcon(firstIcon), + // Utils.buildSvgWithAssets( + // icon: middleIcon, width: 24.h, height: 24.h).paddingAll(8.h), + // buildIcon(lastIcon) + // ], + // ), + // ), + Utils.getPaymentAmountWithSymbol2( + num.tryParse(price) ?? 0.0, + fontSize: 18.f, + letterSpacing:-2 ), - price.toText18(color: AppColors.textColor, weight: FontWeight.w600) ], ); } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart index c1ab8a84..d2e9b211 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart @@ -12,10 +12,15 @@ import 'package:provider/provider.dart' show Consumer; class TypeSelectionWidget extends StatelessWidget { final String hmcCount; final String hmgCount; - final Function(String) onitemClicked; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onitemClicked; const TypeSelectionWidget( - {super.key, required this.hmcCount, required this.hmgCount, required this.onitemClicked}); + {super.key, + required this.hmcCount, + required this.hmgCount, + required this.onitemClicked, + required this.selectedFacility}); @override Widget build(BuildContext context) { @@ -28,51 +33,69 @@ class TypeSelectionWidget extends StatelessWidget { labelText: "All Facilities".needTranslation, shape: RoundedRectangleBorder( side: BorderSide( - color: AppColors.errorColor - , + color: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), backgroundColor: - AppColors.secondaryLightRedColor - , - textColor: AppColors.errorColor + selectedFacility == FacilitySelection.ALL + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor:AppColors.blackColor , ).onPress((){ - onitemClicked(FacilitySelection.ALL.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmg, - iconHasColor: false, - labelText: "Hospitals".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color: AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMG.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmc, - iconHasColor: false, - labelText: "Medical Centers".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color:AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMC.name); + onitemClicked(FacilitySelection.ALL); }), + Visibility( + visible: hmgCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmg, + iconHasColor: false, + labelText: "Hospitals".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMG + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMG); + }), + ), + Visibility( + visible: hmcCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmc, + iconHasColor: false, + labelText: "Medical Centers".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMC + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMC); + }), + ), ], ); } diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index ddea6fd1..5bd0afe2 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -8,10 +8,12 @@ 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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -114,23 +116,8 @@ class EmergencyServicesPage extends StatelessWidget { onPressed: () async { // Navigator.of(context).pop(); - showCommonBottomSheetWithoutHeight( - title: - "Transport Options".needTranslation, - context, - child: AmbulanceOptionSelectionBottomSheet( - onTap: () { - Navigator.of(context).pop(); - context - .read() - .navigateTOAmbulancePage(); - }), - isFullScreen: false, - isCloseButtonVisible: true, - hasBottomPadding: false, - backgroundColor: AppColors.bottomSheetBgColor, - callBackFunc: () {}, - ); + await emergencyServicesViewModel.getTransportationOption(); + openTranportationSelectionBottomSheet(context); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, @@ -276,4 +263,68 @@ class EmergencyServicesPage extends StatelessWidget { ), ); } + + openPickupDetailsBottomSheet(BuildContext context){ + showCommonBottomSheetWithoutHeight( + onCloseClicked: (){ + context + .read() + .flushPickupInformation(); + }, + titleWidget: Transform.flip( + flipX: emergencyServicesViewModel.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + openTranportationSelectionBottomSheet(context); + }), + context, + child: PickupLocation( + onTap: () { + Navigator.of(context).pop(); + context.read().flushSearchPredictions(); + context + .read() + .navigateTOAmbulancePage(); + }), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } + + void openTranportationSelectionBottomSheet(BuildContext context) { + if(emergencyServicesViewModel.transportationOptions.isNotEmpty) { + showCommonBottomSheetWithoutHeight( + title: "Transport Options".needTranslation, + context, + child: SizedBox( + height: 400.h, + child: AmbulanceOptionSelectionBottomSheet( + onTap: () { + Navigator.of(context).pop(); + openPickupDetailsBottomSheet(context); + // context + // .read() + // .navigateTOAmbulancePage(); + }), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } + } } diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index 8138f577..13373f1c 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -7,7 +7,7 @@ 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/emergency_services/emergency_services_view_model.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/nearestERItem.dart' show NearestERItem; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart new file mode 100644 index 00000000..559a0a5d --- /dev/null +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -0,0 +1,118 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' + show AnimationConfiguration, SlideAnimation, FadeInAnimation; +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/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../theme/colors.dart'; +import '../../appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; + +class LocationInputBottomSheet extends StatelessWidget { + final Debouncer debouncer = Debouncer(milliseconds: 500); + + LocationInputBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: "Search Location".needTranslation, + controller: TextEditingController(), + onChange: (value){ + debouncer.run(() { + context.read().updateSearchQuery(value); + }); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + selectionType: SelectionTypeEnum.search, + padding: EdgeInsets.symmetric( + vertical: 10.h, + horizontal: 15.h, + ), + ), + SizedBox(height: 24.h), + Selector>( + selector: (_, vm) => vm.predictions ?? [], + builder: (context, predictions, _) { + if (predictions.isEmpty) return SizedBox.shrink(); + return Expanded( + child: ListView.separated( + separatorBuilder: (_,__)=>SizedBox(height: 16.h,), + itemCount: predictions.length, + itemBuilder: (_, index) { + final prediction = predictions[index]; + + return 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: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: prediction.description.toText14( + color: AppColors.textColor, + weight: FontWeight.w500), + ), + + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, + ), + ], + ).paddingAll(24.h),), + ), + ), + ).onPress(() async { + await context.read().selectPlacePrediction(prediction); + Navigator.of(context).pop(); + var location = context.read().placeDetails; + if(location != null) { + context.read().moveController( + Location(lat: location.lat, lng: location.lng)); + } + }); + + }, + ), + ); + }, + ) + ], + ); + } +} diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index 3dc2aa1f..bbce56a4 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -5,7 +5,7 @@ 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/emergency_services/emergency_services_view_model.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.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'; diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 9f1f503f..cf8918c9 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -62,16 +62,20 @@ class DialogServiceImp implements DialogService { message: message, showCancel: onCancelPressed != null ? true : false, onOkPressed: () { + print('ok button is pressed'); if (onOkPressed != null) { + print('onOkPressed is not null'); onOkPressed(); + }else { + Navigator.pop(context); } - context.pop(); }, onCancelPressed: () { if (onCancelPressed != null) { onCancelPressed(); + }else { + Navigator.pop(context); } - context.pop(); }, ), ); diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index ce2f87c1..6ccf8b16 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -63,6 +63,8 @@ static const Color alertLightColor = Color(0xFFD48D05); static const Color infoLightColor = Color(0xFF0B85F7); static const Color warningLightColor = Color(0xFFFFCC00); static const Color greyLightColor = Color(0xFFEFEFF0); +static const Color thumbColor = Color(0xFF18C273); +static const Color switchBackgroundColor = Color(0x2618C273); static const Color bottomNAVBorder = Color(0xFFEEEEEE); diff --git a/lib/widgets/CustomSwitch.dart b/lib/widgets/CustomSwitch.dart index 8e3364b5..784e446c 100644 --- a/lib/widgets/CustomSwitch.dart +++ b/lib/widgets/CustomSwitch.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class CustomSwitch extends StatefulWidget { final bool value; @@ -17,21 +18,21 @@ class _CustomSwitchState extends State { return GestureDetector( onTap: () => widget.onChanged(!widget.value), child: Container( - width: 48.h, + width: 48.w, height: 30.h, decoration: BoxDecoration( - color: widget.value ? Color(0xFFE6F7F0) : Color(0xFFE6F7F0), + color: AppColors.switchBackgroundColor , borderRadius: BorderRadius.circular(18), ), child: AnimatedAlign( - duration: Duration(milliseconds: 200), + duration: Duration(milliseconds: 300), alignment: widget.value ? Alignment.centerRight : Alignment.centerLeft, child: Container( - margin: EdgeInsets.all(4), - width: 28, - height: 28, + margin: EdgeInsets.all(2.h), + width: 28.w, + height: 28.h, decoration: BoxDecoration( - color: Color(0xFF5FCB89), + color: AppColors.thumbColor, shape: BoxShape.circle, ), ), diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 4d0dcf12..3aaefe75 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -116,6 +116,7 @@ void showCommonBottomSheetWithoutHeight( bool useSafeArea = false, bool hasBottomPadding = true, Color backgroundColor = AppColors.bottomSheetBgColor, + VoidCallback? onCloseClicked }) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( @@ -170,6 +171,7 @@ void showCommonBottomSheetWithoutHeight( icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E), ).onPress(() { + onCloseClicked?.call(); Navigator.of(context).pop(); }), ], diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 2dcd32f9..d943731c 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -113,7 +113,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, - height: 58.h, + height: 64.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 852af41f..f655479c 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -1,41 +1,63 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:huawei_map/huawei_map.dart' ; class HMSMap extends StatefulWidget{ final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; + final VoidCallback onCameraIdle; final MapType mapType; final bool compassEnabled; final bool myLocationEnabled; + final bool showCenterMarker; + final Completer? inputController; - HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved, this.mapType = MapType.normal,this.compassEnabled = false, this.myLocationEnabled = true}); + HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, + this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, + this.myLocationEnabled = true, this.inputController}); @override State createState() => _HMSMapState(); } class _HMSMapState extends State { - final Completer _controller = Completer(); + Completer? controller; @override void initState() { HuaweiMapInitializer.initializeMap(); + controller = widget.inputController ?? Completer(); super.initState(); } // @override @override Widget build(BuildContext context) => - HuaweiMap( - mapType: widget.mapType, - zoomControlsEnabled: false, - myLocationEnabled: widget.myLocationEnabled, - compassEnabled: widget.compassEnabled, - initialCameraPosition: widget.currentLocation, - onCameraMove: (value) => widget.onCameraMoved(value), - onMapCreated: (HuaweiMapController controller) { - _controller.complete(controller); - }, - ); + Stack( + children: [ + HuaweiMap( + mapType: widget.mapType, + zoomControlsEnabled: false, + myLocationEnabled: widget.myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: widget.compassEnabled, + onCameraIdle:()=> widget.onCameraIdle(), + initialCameraPosition: widget.currentLocation, + onCameraMove: (value) => widget.onCameraMoved(value), + onMapCreated: (HuaweiMapController controller) { + this.controller?.complete(controller); + }, + ), + Visibility( + visible: widget.showCenterMarker, + child: Align( + alignment: Alignment.center, + child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + ), + ) + ], + ); } \ No newline at end of file diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/map.dart index cd068385..0c67f1b6 100644 --- a/lib/widgets/map/map.dart +++ b/lib/widgets/map/map.dart @@ -2,31 +2,52 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.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'; class GMSMap extends StatelessWidget{ - final Completer _controller = Completer(); + Completer? controller; final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; + final VoidCallback onCameraIdle; final MapType mapType; final bool compassEnabled; final bool myLocationEnabled; + final bool showCenterMarker; - GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved, this.mapType = MapType.normal,this.compassEnabled = false, this.myLocationEnabled = true}); + GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, + this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, + this.myLocationEnabled = true,Completer? inputController}){ + controller = inputController ?? Completer(); + } @override Widget build(BuildContext context) { - return GoogleMap( - mapType: mapType, - zoomControlsEnabled: false, - myLocationEnabled: myLocationEnabled, - compassEnabled: compassEnabled, - initialCameraPosition: currentLocation, - onCameraMove: (value) => onCameraMoved(value), - onMapCreated: (GoogleMapController controller) { - - _controller.complete(controller); - }, + return Stack( + children: [ + GoogleMap( + mapType: mapType, + zoomControlsEnabled: false, + myLocationEnabled: myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: compassEnabled, + initialCameraPosition: currentLocation, + onCameraMove: (value) => onCameraMoved(value), + onCameraIdle: ()=>onCameraIdle(), + onMapCreated: (GoogleMapController controller) { + this.controller?.complete(controller); + }, + ), + Visibility( + visible: showCenterMarker, + child: Align( + alignment: Alignment.center, + child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + ), + ) + ], ); } } \ No newline at end of file