Merge pull request 'nearest er module added' (#84) from nearest_er into master

Reviewed-on: #84
ambulance_ui
Haroon6138 3 weeks ago
commit 916ebf2880

@ -0,0 +1,3 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99996 1.22925C3.53667 1.22925 0.729126 4.0368 0.729126 7.50008C0.729126 10.9634 3.53667 13.7709 6.99996 13.7709C10.4632 13.7709 13.2708 10.9634 13.2708 7.50008C13.2708 4.0368 10.4632 1.22925 6.99996 1.22925ZM9.74577 5.57923C9.97358 5.35142 9.97358 4.98207 9.74577 4.75427C9.51797 4.52646 9.14862 4.52646 8.92081 4.75427L7 6.67509L5.95409 5.62925C5.72628 5.40146 5.35693 5.40147 5.12913 5.62928C4.90133 5.8571 4.90135 6.22644 5.12916 6.45424L6.17504 7.50004L6.00415 7.67094C5.77634 7.89874 5.77634 8.26809 6.00415 8.49589C6.23195 8.7237 6.6013 8.7237 6.8291 8.49589L7.00002 8.32497L7.17076 8.49569C7.39857 8.72349 7.76792 8.72348 7.99571 8.49567C8.22351 8.26785 8.2235 7.89851 7.99569 7.67071L7.82498 7.50002L9.74577 5.57923Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 902 B

@ -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';

@ -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<HabibWalletRepo>(() => HabibWalletRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MedicalFileRepo>(() => MedicalFileRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ImmediateLiveCareRepo>(() => ImmediateLiveCareRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<EmergencyServicesRepo>(() =>
EmergencyServicesRepoImpl(
loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -186,17 +192,13 @@ class AppDependencies {
() => DoctorFilterViewModel(),
);
getIt.registerLazySingleton<AppointmentViaRegionViewmodel>(
() => AppointmentViaRegionViewmodel(navigationService: getIt(), appState: getIt()),
getIt.registerLazySingleton<EmergencyServicesViewModel>(
() => EmergencyServicesViewModel(
locationUtils: getIt(),
navServices: getIt(),
viewModel: getIt(),
appState: getIt(),
),
);
// Screen-specific VMs Factory
// getIt.registerFactory<BookAppointmentsViewModel>(
// () => BookAppointmentsViewModel(
// bookAppointmentsRepo: getIt(),
// dialogService: getIt(),
// errorHandlerService: getIt(),
// ),
// );
}
}

@ -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;

@ -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();
}
}

@ -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);
},
);
}

@ -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<Either<Failure, GenericApiModel<List<ProjectAvgERWaitingTime>>>> getNearestEr({int? id, int? projectID});
}
class EmergencyServicesRepoImpl implements EmergencyServicesRepo {
final ApiClient apiClient;
final LoggerService loggerService;
EmergencyServicesRepoImpl({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<ProjectAvgERWaitingTime>>>> getNearestEr({int? id, int? projectID}) async {
Map<String, dynamic> mapDevice = {'IsForER' : true};
try {
GenericApiModel<List<ProjectAvgERWaitingTime>>? 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<String, dynamic>)).toList().cast<ProjectAvgERWaitingTime>();
apiResponse = GenericApiModel<List<ProjectAvgERWaitingTime>>(
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()));
}
}
}

@ -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<ProjectAvgERWaitingTime> nearestERList = [];
List<ProjectAvgERWaitingTime> 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<void> 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<void> 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<void> 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;
}
},
);
}
}

@ -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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> 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<String, dynamic> toJson() {
// final Map<String, dynamic> data = new Map<String, dynamic>();
// 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;
// }
//}

@ -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<DoctorFilterViewModel>(
create: (_) => getIt.get<DoctorFilterViewModel>(),
),
ChangeNotifierProvider<EmergencyServicesViewModel>(
create: (_) => getIt.get<EmergencyServicesViewModel>(),
)
], child: MyApp()),
),

@ -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>();
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<EmergencyServicesViewModel>().navigateToNearestERPage();
}),
),
SizedBox(height: 16.h),

@ -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<NearestErPage> {
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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel,
bool>(
selector: (___, viewModel) => viewModel.isERListLoading,
builder: (_, isLoading, __) =>
Selector<EmergencyServicesViewModel,
List<ProjectAvgERWaitingTime>>(
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),
)
);
}
}

@ -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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().openDialer( nearestERItem.phonenumber??"");
},
backgroundColor: AppColors.greyColor,
iconColor: AppColors.textColor,
borderColor: AppColors.greyColor,
height: 40.h,
).toShimmer2(isShow: isLoading),
),
],
),
],
),
),
);
}
}

@ -67,10 +67,13 @@ 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(
Visibility(
visible: text.isNotEmpty,
child: Padding(
padding: EdgeInsets.only(top: 0),
child: Text(
text,
@ -83,6 +86,7 @@ class CustomButton extends StatelessWidget {
),
),
),
),
],
),
));

Loading…
Cancel
Save