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 71c4e22..e194bb3 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 235e8f4..5a510d1 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -40,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'; @@ -183,15 +184,23 @@ class AppDependencies { () => DoctorFilterViewModel(), ); - getIt.registerLazySingleton( - () => AppointmentViaRegionViewmodel( - navigationService: getIt(), + getIt.registerLazySingleton( + () => EmergencyServicesViewModel( + locationUtils: getIt(), + navServices: getIt(), + emergencyServicesRepo: getIt(), appState: getIt(), + errorHandlerService: getIt(), ), ); - getIt.registerLazySingleton( - () => EmergencyServicesViewModel(errorHandlerService: getIt(), emergencyServicesRepo: 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..487b228 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -36,21 +36,60 @@ class LocationUtils { isGMSDevice = GmsCheck().checkGmsAvailability(); } - void getLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + // final defaultCallbackForLocationDenied = (){ + // 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 better results" + // .needTranslation, + // isShowActionButtons: true, + // onCancelTap: () { + // navigationService.pop(); + // }, + // onConfirmTap: () async { + // navigationService.pop(); + // openAppSettings(); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } + + void getLocation( + {Function(LatLng)? onSuccess, + VoidCallback? onFailure, + bool isShowConfirmDialog = false, + VoidCallback? onLocationDeniedForever}) async { + this.isShowConfirmDialog = isShowConfirmDialog; if (Platform.isIOS) { - getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); + getCurrentLocation( + onFailure: onFailure, + onSuccess: onSuccess, + onLocationDeniedForever: onLocationDeniedForever); return; } if (await isGMSDevice ?? true) { - getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); + getCurrentLocation( + onFailure: onFailure, + onSuccess: onSuccess, + onLocationDeniedForever: onLocationDeniedForever); return; } - getHMSLocation(onFailure: onFailure, onSuccess: onSuccess); + getHMSLocation( + onFailure: onFailure, + onSuccess: onSuccess, + onLocationDeniedForever: onLocationDeniedForever); } - void getCurrentLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + void getCurrentLocation( + {Function(LatLng)? onSuccess, + VoidCallback? onFailure, + VoidCallback? onLocationDeniedForever}) async { var location = Location(); bool isLocationEnabled = await location.serviceEnabled(); @@ -73,12 +112,15 @@ class LocationUtils { return; } } else if (permissionGranted == LocationPermission.deniedForever) { - if (isShowConfirmDialog) { + appState.resetLocation(); + if(onLocationDeniedForever == 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(); @@ -92,6 +134,8 @@ class LocationUtils { isCloseButtonVisible: true, ); } + onLocationDeniedForever?.call(); + return; } Position? currentLocation = await Geolocator.getLastKnownPosition(); @@ -209,7 +253,10 @@ class LocationUtils { appState.userLong = locationData.longitude; } - void getHMSLocation({VoidCallback? onFailure, Function(LatLng p1)? onSuccess}) async { + void getHMSLocation( + {VoidCallback? onFailure, + Function(LatLng p1)? onSuccess, + VoidCallback? onLocationDeniedForever}) async { try { var location = Location(); HmsLocation.FusedLocationProviderClient locationService = HmsLocation.FusedLocationProviderClient()..initFusedLocationService(); @@ -230,7 +277,32 @@ class LocationUtils { LocationPermission permissionGranted = await Geolocator.checkPermission(); if (permissionGranted == LocationPermission.denied) { permissionGranted = await Geolocator.requestPermission(); - if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { + if (permissionGranted == LocationPermission.deniedForever) { + appState.resetLocation(); + if(onLocationDeniedForever == 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 better results" + .needTranslation, + isShowActionButtons: true, + onCancelTap: () { + navigationService.pop(); + }, + onConfirmTap: () async { + navigationService.pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + onLocationDeniedForever?.call(); + return; + } else if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { appState.resetLocation(); onFailure?.call(); return; 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/core/utils/utils.dart b/lib/core/utils/utils.dart index c4a2db8..ae49018 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -643,12 +643,17 @@ class Utils { } /// Widget to build an SVG from network - static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover}) { + static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover, ImageErrorWidgetBuilder? errorBuilder}) { return Image.network( url, width: width, height: height, fit: fit, + errorBuilder: errorBuilder??(_,__,___){ + //todo change the error builder icon that it is returning + return Utils.buildSvgWithAssets(width: width, + height: height,icon: AppAssets.no_visit_icon); + }, ); } diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 129ee4e..089e7ad 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -3,11 +3,14 @@ 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/services/logger_service.dart'; abstract class EmergencyServicesRepo { Future>>> getRRTProcedures(); + + Future>>> getNearestEr({int? id, int? projectID}); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -16,6 +19,42 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { EmergencyServicesRepoImp({required this.loggerService, required this.apiClient}); + 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())); + } + } + @override Future>>> getRRTProcedures() async { Map mapDevice = {}; diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 1a95062..d121fe5 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -1,12 +1,28 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +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/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.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/routes/custom_page_route.dart'; +import 'package:url_launcher/url_launcher.dart'; class EmergencyServicesViewModel extends ChangeNotifier { EmergencyServicesRepo emergencyServicesRepo; ErrorHandlerService errorHandlerService; + final NavigationService navServices; + final LocationUtils? locationUtils; + final AppState appState; + bool isERListLoading = false; + List nearestERList = []; + List nearestERFilteredList = []; + List RRTProceduresList = []; late RRTProceduresResponseModel selectedRRTProcedure; @@ -16,7 +32,13 @@ class EmergencyServicesViewModel extends ChangeNotifier { notifyListeners(); } - EmergencyServicesViewModel({required this.emergencyServicesRepo, required this.errorHandlerService}); + EmergencyServicesViewModel({ + required this.emergencyServicesRepo, + required this.errorHandlerService, + required this.navServices, + required this.locationUtils, + required this.appState, + }); Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { RRTProceduresList.clear(); @@ -40,4 +62,76 @@ class EmergencyServicesViewModel extends ChangeNotifier { }, ); } + + 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 emergencyServicesRepo.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/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index e7ba5fa..adf141a 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -22,9 +22,8 @@ import 'package:provider/provider.dart'; class EmergencyServicesPage extends StatelessWidget { EmergencyServicesPage({super.key}); - LocationUtils? locationUtils; - late EmergencyServicesViewModel emergencyServicesViewModel; + LocationUtils? locationUtils; @override Widget build(BuildContext context) { @@ -89,13 +88,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..8138f57 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -1,5 +1,21 @@ +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 +25,80 @@ 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 f5ac258..5cfe3de 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, + ), ), ), ),