From c146747e5ec511dc1db194b0506eaed4c225de44 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 12 Oct 2025 16:20:42 +0300 Subject: [PATCH] nearest er clinic repo viewmodel and view added --- assets/images/svg/waiting_time_clock.svg | 3 + lib/core/app_assets.dart | 1 + lib/core/dependencies.dart | 24 ++-- lib/core/location_util.dart | 14 +- lib/core/utils/debouncer.dart | 19 +++ .../emergency_services_repo.dart | 56 ++++++++ .../emergency_services_view_model.dart | 103 ++++++++++++++ .../resp_model/ProjectAvgERWaitingTime.dart | 114 +++++++++++++++ lib/main.dart | 4 + .../emergency_services_page.dart | 16 +-- .../emergency_services/nearest_er_page.dart | 94 ++++++++++++- .../widgets/nearestERItem.dart | 130 ++++++++++++++++++ lib/widgets/buttons/custom_button.dart | 26 ++-- 13 files changed, 565 insertions(+), 39 deletions(-) create mode 100644 assets/images/svg/waiting_time_clock.svg create mode 100644 lib/core/utils/debouncer.dart create mode 100644 lib/features/emergency_services/emergency_services_repo.dart create mode 100644 lib/features/emergency_services/emergency_services_view_model.dart create mode 100644 lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart create mode 100644 lib/presentation/emergency_services/widgets/nearestERItem.dart diff --git a/assets/images/svg/waiting_time_clock.svg b/assets/images/svg/waiting_time_clock.svg new file mode 100644 index 0000000..6207841 --- /dev/null +++ b/assets/images/svg/waiting_time_clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index b675e60..b194914 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -158,6 +158,7 @@ class AppAssets { static const String call_ambulance_icon = '$svgBasePath/call_ambulance_icon.svg'; static const String nearest_er_icon = '$svgBasePath/nearest_er_icon.svg'; static const String rrt_icon = '$svgBasePath/rrt_icon.svg'; + static const String waiting_time_clock = '$svgBasePath/waiting_time_clock.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 7337dfa..2de1593 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; @@ -38,6 +40,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; +import 'package:http/http.dart'; import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -99,6 +102,9 @@ class AppDependencies { getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => + EmergencyServicesRepoImpl( + loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -186,17 +192,13 @@ class AppDependencies { () => DoctorFilterViewModel(), ); - getIt.registerLazySingleton( - () => AppointmentViaRegionViewmodel(navigationService: getIt(), appState: getIt()), + getIt.registerLazySingleton( + () => EmergencyServicesViewModel( + locationUtils: getIt(), + navServices: getIt(), + viewModel: getIt(), + appState: getIt(), + ), ); - - // Screen-specific VMs → Factory - // getIt.registerFactory( - // () => BookAppointmentsViewModel( - // bookAppointmentsRepo: getIt(), - // dialogService: getIt(), - // errorHandlerService: getIt(), - // ), - // ); } } diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index 61c581e..567bc59 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -36,7 +36,8 @@ class LocationUtils { isGMSDevice = GmsCheck().checkGmsAvailability(); } - void getLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + void getLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure,bool isShowConfirmDialog = false}) async { + this.isShowConfirmDialog = isShowConfirmDialog; if (Platform.isIOS) { getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); return; @@ -50,7 +51,7 @@ class LocationUtils { getHMSLocation(onFailure: onFailure, onSuccess: onSuccess); } - void getCurrentLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + void getCurrentLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure, }) async { var location = Location(); bool isLocationEnabled = await location.serviceEnabled(); @@ -73,12 +74,14 @@ class LocationUtils { return; } } else if (permissionGranted == LocationPermission.deniedForever) { - if (isShowConfirmDialog) { + if (onFailure == null && isShowConfirmDialog) { showCommonBottomSheetWithoutHeight( title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see the nearest ER location details".needTranslation, + loadingText: + "Please grant location permission from app settings to see better results" + .needTranslation, isShowActionButtons: true, onCancelTap: () { navigationService.pop(); @@ -91,7 +94,10 @@ class LocationUtils { isFullScreen: false, isCloseButtonVisible: true, ); + return; } + onFailure?.call(); + return; } Position? currentLocation = await Geolocator.getLastKnownPosition(); diff --git a/lib/core/utils/debouncer.dart b/lib/core/utils/debouncer.dart new file mode 100644 index 0000000..529e0a3 --- /dev/null +++ b/lib/core/utils/debouncer.dart @@ -0,0 +1,19 @@ +import 'dart:async'; +import 'dart:ui'; + +class Debouncer { + final int milliseconds; + VoidCallback? action; + Timer? _timer; + + Debouncer({required this.milliseconds}); + + void run(VoidCallback action) { + _timer?.cancel(); + _timer = Timer(Duration(milliseconds: milliseconds), action); + } + + void dispose() { + _timer?.cancel(); + } +} \ No newline at end of file diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart new file mode 100644 index 0000000..0f35fed --- /dev/null +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -0,0 +1,56 @@ +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/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +import '../../core/api_consts.dart' show GET_NEAREST_HOSPITAL; + +abstract interface class EmergencyServicesRepo { + Future>>> getNearestEr({int? id, int? projectID}); +} + + +class EmergencyServicesRepoImpl implements EmergencyServicesRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + EmergencyServicesRepoImpl({required this.loggerService, required this.apiClient}); + @override + Future>>> getNearestEr({int? id, int? projectID}) async { + Map mapDevice = {'IsForER' : true}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_NEAREST_HOSPITAL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_ProjectAvgERWaitingTime']; + + final clinicsList = list.map((item) => ProjectAvgERWaitingTime.fromJson(item as Map)).toList().cast(); + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } 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 new file mode 100644 index 0000000..ce27555 --- /dev/null +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -0,0 +1,103 @@ +import 'dart:async' show FutureOr; + +import 'package:flutter/foundation.dart' show ChangeNotifier; +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/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/presentation/emergency_services/nearest_er_page.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:url_launcher/url_launcher.dart' show canLaunchUrl, launchUrl, LaunchMode; + +class EmergencyServicesViewModel extends ChangeNotifier { + final NavigationService navServices; + final LocationUtils? locationUtils; + final EmergencyServicesRepo viewModel; + final AppState appState; + bool isERListLoading = false; + List nearestERList = []; + List nearestERFilteredList = []; + + EmergencyServicesViewModel( + {required this.navServices, + required this.locationUtils, + required this.viewModel, + required this.appState}); + + void navigateToNearestERPage() { + locationUtils!.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) { + navServices.push( + CustomPageRoute( + page: NearestErPage(), + ), + ); + getNearestER(); + }); + } + + void filterErList(String query) { + print ("the query is $query"); + if (query.isEmpty) { + nearestERFilteredList = nearestERList; + } else { + nearestERFilteredList = nearestERList + .where((er) => + er.projectName != null && + er.projectName!.toLowerCase().contains(query.toLowerCase())) + .toList(); + } + notifyListeners(); + } + + // Open directions (navigation) from current location to destination. + Future openDirections({ + required double destLat, + required double destLng, + String? travelMode, // driving, walking, bicycling, transit + }) async { + // Try Google Maps app navigation intent (android/iOS) + final modeParam = travelMode == null ? 'driving' : travelMode; + final googleNavUri = Uri.parse('google.navigation:q=$destLat,$destLng&mode=${modeParam.substring(0,1)}'); // mode: d/w/b/t by scheme + final universalUrl = Uri.parse('https://www.google.com/maps/dir/?api=1&destination=$destLat,$destLng&travelmode=$modeParam'); + + if (await canLaunchUrl(googleNavUri)) { + await launchUrl(googleNavUri, mode: LaunchMode.externalApplication); + } else { + await launchUrl(universalUrl, mode: LaunchMode.externalApplication); + } + } + + Future openDialer(String phoneNumber) async { + final Uri telUri = Uri(scheme: 'tel', path: phoneNumber); + + if (await canLaunchUrl(telUri)) { + await launchUrl(telUri, mode: LaunchMode.externalApplication); + } else { + throw 'Could not open dialer for $phoneNumber'; + } + } + FutureOr getNearestER() async { + isERListLoading = true; + nearestERList = []; + nearestERFilteredList = []; + notifyListeners(); + var response = await viewModel.getNearestEr(); + isERListLoading = false; + notifyListeners(); + + response.fold( + (failure) async { + }, + (apiResponse) { + isERListLoading = false; + if (apiResponse.messageStatus == 1) { + nearestERList = apiResponse.data!; + nearestERFilteredList = nearestERList; + } + }, + ); + } +} diff --git a/lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart b/lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart new file mode 100644 index 0000000..57d73ae --- /dev/null +++ b/lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart @@ -0,0 +1,114 @@ +class ProjectAvgERWaitingTime { + int? iD; + int? projectID; + int? avgTimeInMinutes; + String? avgTimeInHHMM; + dynamic distanceInKilometers; + String? latitude; + String? longitude; + String? phonenumber; + String? projectImageURL; + String? projectName; + + ProjectAvgERWaitingTime( + {this.iD, + this.projectID, + this.avgTimeInMinutes, + this.avgTimeInHHMM, + this.distanceInKilometers, + this.latitude, + this.longitude, + this.phonenumber, + this.projectImageURL, + this.projectName}); + + ProjectAvgERWaitingTime.fromJson(Map json) { + iD = json['ID']; + projectID = json['ProjectID']; + avgTimeInMinutes = json['AvgTimeInMinutes']; + avgTimeInHHMM = json['AvgTimeInHHMM']; + distanceInKilometers = json['DistanceInKilometers']; + latitude = json['Latitude']; + longitude = json['Longitude']; + phonenumber = json['PhoneNumber']; + projectImageURL = json['ProjectImageURL']; + projectName = json['ProjectName']; + } + + String getTime(){ + print("the name is $projectName"); + print("the avgTimeInMinutes is $avgTimeInMinutes"); + if(avgTimeInMinutes == null) return ""; + int hours = avgTimeInMinutes! ~/ 60; + int minutes = avgTimeInMinutes! % 60; + print("the time is ${"${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}"}"); + return "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}"; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ProjectID'] = this.projectID; + data['AvgTimeInMinutes'] = this.avgTimeInMinutes; + data['AvgTimeInHHMM'] = this.avgTimeInHHMM; + data['DistanceInKilometers'] = this.distanceInKilometers; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['PhoneNumber'] = this.phonenumber; + data['ProjectImageURL'] = this.projectImageURL; + data['ProjectName'] = this.projectName; + return data; + } +} +//class ProjectAvgERWaitingTime { +// int? iD; +// int? projectID; +// int? avgTimeInMinutes; +// String? avgTimeInHHMM; +// String? distanceInKilometers; +// String? latitude; +// String? longitude; +// String? phonenum?ber; +// String? projectImageURL; +// String? projectName; +// +// ProjectAvgERWaitingTime( +// {this.iD, +// this.projectID, +// this.avgTimeInMinutes, +// this.avgTimeInHHMM, +// this.distanceInKilometers, +// this.latitude, +// this.longitude, +// this.phonenum?ber, +// this.projectImageURL, +// this.projectName}); +// +// ProjectAvgERWaitingTime.fromJson(Map json) { +// iD = json['ID']; +// projectID = json['ProjectID']; +// avgTimeInMinutes = json['AvgTimeInMinutes']; +// avgTimeInHHMM = json['AvgTimeInHHMM']; +// distanceInKilometers = json['DistanceInKilometers']; +// latitude = json['Latitude']; +// longitude = json['Longitude']; +// phonenum?ber = json['Phonenum?ber']; +// projectImageURL = json['ProjectImageURL']; +// projectName = json['ProjectName']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['ID'] = this.iD; +// data['ProjectID'] = this.projectID; +// data['AvgTimeInMinutes'] = this.avgTimeInMinutes; +// data['AvgTimeInHHMM'] = this.avgTimeInHHMM; +// data['DistanceInKilometers'] = this.distanceInKilometers; +// data['Latitude'] = this.latitude; +// data['Longitude'] = this.longitude; +// data['Phonenum?ber'] = this.phonenum?ber; +// data['ProjectImageURL'] = this.projectImageURL; +// data['ProjectName'] = this.projectName; +// return data; +// } +//} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 20507d0..a8f1a96 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; @@ -125,6 +126,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 0052930..bd9785f 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -6,21 +6,21 @@ 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/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; class EmergencyServicesPage extends StatelessWidget { - EmergencyServicesPage({super.key}); + const EmergencyServicesPage({super.key}); + - LocationUtils? locationUtils; @override Widget build(BuildContext context) { - locationUtils = getIt.get(); - locationUtils!.isShowConfirmDialog = true; return CollapsingListView( title: "Emergency Services", requests: () {}, @@ -79,13 +79,7 @@ class EmergencyServicesPage extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), ], ).onPress(() { - locationUtils!.getLocation(onSuccess: (position) { - Navigator.of(context).push( - CustomPageRoute( - page: NearestErPage(), - ), - ); - }); + context.read().navigateToNearestERPage(); }), ), SizedBox(height: 16.h), diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index ebf1c91..b6a1f65 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -1,5 +1,22 @@ +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/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/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/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'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart' show Selector, WatchContext, ReadContext; + +import '../../core/enums.dart' show SelectionTypeEnum; +import '../../core/utils/debouncer.dart' show Debouncer; class NearestErPage extends StatefulWidget { const NearestErPage({super.key}); @@ -9,11 +26,84 @@ class NearestErPage extends StatefulWidget { } class _NearestErPageState extends State { + final TextEditingController searchText = TextEditingController(); + final Debouncer debouncer = Debouncer(milliseconds: 500); + @override + void dispose() { + debouncer.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { return CollapsingListView( - title: "Nearest ER", - child: Container(), + title: "Nearest ER".needTranslation, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: 'Type any facility name'.needTranslation, + controller: searchText, + onChange: (value) { + debouncer.run((){ + context.read().filterErList(value??''); + }); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + leadingIcon: AppAssets.search_icon, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + Selector( + selector: (___, viewModel) => viewModel.isERListLoading, + builder: (_, isLoading, __) => + Selector>( + selector: (___, viewModel) => viewModel.nearestERFilteredList, + shouldRebuild: (previous, next) => previous != next, + builder: (_, value, __) { + if (isLoading || value.isNotEmpty) { + return ListView.separated( + padding: EdgeInsets.only(top: 24.h), + itemCount: isLoading ? 6 : value.length, + separatorBuilder: (____, _____) => + SizedBox(height: 16.h,), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation(child: NearestERItem( + isLoading: isLoading, + nearestERItem: isLoading + ? ProjectAvgERWaitingTime() + : value[index], + )))); + }, + ); + }else { + return Center(child: Utils.getNoDataWidget(context, noDataText: "No nearest Er Arround you".needTranslation)); + } + + } + ), + ), + ], + ).paddingAll(16.h), + ) ); } } diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart new file mode 100644 index 0000000..3dc2aa1 --- /dev/null +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -0,0 +1,130 @@ +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: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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +class NearestERItem extends StatelessWidget { + final ProjectAvgERWaitingTime nearestERItem; + final bool isLoading; + + + const NearestERItem({ super.key, + required this.nearestERItem, + required this.isLoading + }) ; + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + (isLoading || nearestERItem.projectImageURL?.isEmpty == true) + ? Container( + width: 24.h, + height: 24.h, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + ).toShimmer2(isShow: isLoading) + : Utils.buildImgWithNetwork( + url: nearestERItem.projectImageURL ?? '', + iconColor: Colors.transparent, + ).circle(24.h).toShimmer2(isShow: isLoading), + const SizedBox(width: 12), + Expanded( + child: (nearestERItem.projectName?.toText16( + color: AppColors.textColor, + weight: FontWeight.w600, + ) ?? + SizedBox.shrink()).toShimmer2(isShow: isLoading), + ), + // TODO: Add hospital icon logic here if needed + ], + ), + SizedBox(height: 8.h), + Row( + spacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: "${nearestERItem.distanceInKilometers} km".needTranslation, + icon: AppAssets.location, + iconHasColor: false, + labelPadding: EdgeInsetsDirectional.only(start: 4.h, end: 0.h), + padding: EdgeInsets.all(8.h), + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "Expected waiting time: ${nearestERItem.getTime()} mins".needTranslation, + icon: AppAssets.waiting_time_clock, + iconHasColor: false, + labelPadding: EdgeInsetsDirectional.only(start: 4.h, end: 0.h), + padding: EdgeInsets.all(8.h), + ).toShimmer2(isShow: isLoading), + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "View Location on Google Maps".needTranslation, + iconSize: 18.h, + icon: AppAssets.location, + onPressed: () { + context.read().openDirections(destLat: double.parse(nearestERItem.latitude??"0.0"), destLng: double.parse(nearestERItem.longitude??"0.0") ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + height: 40.h, + fontSize: 14, + fontWeight: FontWeight.w500, + ).toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.h), + SizedBox( + height: 40.h, + width: 40.h, + child: CustomButton( + text: '', + iconSize: 18.h, + icon: AppAssets.call_fill, + onPressed: () { + context.read().openDialer( nearestERItem.phonenumber??""); + + }, + backgroundColor: AppColors.greyColor, + iconColor: AppColors.textColor, + borderColor: AppColors.greyColor, + height: 40.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 73885b0..4653878 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -67,19 +67,23 @@ class CustomButton extends StatelessWidget { children: [ if (icon != null) Padding( - padding: EdgeInsets.only(right: 8.h, left: 8.h), + padding: text.isNotEmpty ?EdgeInsets.only(right: 8.h, left: 8.h): EdgeInsets.zero, child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), ), - Padding( - padding: EdgeInsets.only(top: 0), - child: Text( - text, - overflow: textOverflow, - style: context.dynamicTextStyle( - fontSize: fontSize.fSize, - color: isDisabled ? textColor.withOpacity(0.5) : textColor, - letterSpacing: 0, - fontWeight: fontWeight, + + Visibility( + visible: text.isNotEmpty, + child: Padding( + padding: EdgeInsets.only(top: 0), + child: Text( + text, + overflow: textOverflow, + style: context.dynamicTextStyle( + fontSize: fontSize.fSize, + color: isDisabled ? textColor.withOpacity(0.5) : textColor, + letterSpacing: 0, + fontWeight: fontWeight, + ), ), ), ),