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..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
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 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,
+ ),
),
),
),