Merge branch 'master' into haroon_dev

# Conflicts:
#	lib/core/dependencies.dart
#	lib/features/emergency_services/emergency_services_repo.dart
#	lib/features/emergency_services/emergency_services_view_model.dart
#	lib/presentation/emergency_services/emergency_services_page.dart
pull/85/head
haroon amjad 3 weeks ago
commit 1c3ed8f2e1

@ -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 call_ambulance_icon = '$svgBasePath/call_ambulance_icon.svg';
static const String nearest_er_icon = '$svgBasePath/nearest_er_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 rrt_icon = '$svgBasePath/rrt_icon.svg';
static const String waiting_time_clock = '$svgBasePath/waiting_time_clock.svg';
//bottom navigation// //bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String homeBottom = '$svgBasePath/home_bottom.svg';

@ -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/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_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: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:local_auth/local_auth.dart';
import 'package:logger/web.dart'; import 'package:logger/web.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -183,15 +184,23 @@ class AppDependencies {
() => DoctorFilterViewModel(), () => DoctorFilterViewModel(),
); );
getIt.registerLazySingleton<AppointmentViaRegionViewmodel>( getIt.registerLazySingleton<EmergencyServicesViewModel>(
() => AppointmentViaRegionViewmodel( () => EmergencyServicesViewModel(
navigationService: getIt(), locationUtils: getIt(),
navServices: getIt(),
emergencyServicesRepo: getIt(),
appState: getIt(), appState: getIt(),
errorHandlerService: getIt(),
), ),
); );
getIt.registerLazySingleton<EmergencyServicesViewModel>( // Screen-specific VMs Factory
() => EmergencyServicesViewModel(errorHandlerService: getIt(), emergencyServicesRepo: getIt()), // getIt.registerFactory<BookAppointmentsViewModel>(
); // () => BookAppointmentsViewModel(
// bookAppointmentsRepo: getIt(),
// dialogService: getIt(),
// errorHandlerService: getIt(),
// ),
// );
} }
} }

@ -36,21 +36,60 @@ class LocationUtils {
isGMSDevice = GmsCheck().checkGmsAvailability(); 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) { if (Platform.isIOS) {
getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); getCurrentLocation(
onFailure: onFailure,
onSuccess: onSuccess,
onLocationDeniedForever: onLocationDeniedForever);
return; return;
} }
if (await isGMSDevice ?? true) { if (await isGMSDevice ?? true) {
getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); getCurrentLocation(
onFailure: onFailure,
onSuccess: onSuccess,
onLocationDeniedForever: onLocationDeniedForever);
return; 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(); var location = Location();
bool isLocationEnabled = await location.serviceEnabled(); bool isLocationEnabled = await location.serviceEnabled();
@ -73,12 +112,15 @@ class LocationUtils {
return; return;
} }
} else if (permissionGranted == LocationPermission.deniedForever) { } else if (permissionGranted == LocationPermission.deniedForever) {
if (isShowConfirmDialog) { appState.resetLocation();
if(onLocationDeniedForever == null && isShowConfirmDialog){
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!, navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget( 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, isShowActionButtons: true,
onCancelTap: () { onCancelTap: () {
navigationService.pop(); navigationService.pop();
@ -92,6 +134,8 @@ class LocationUtils {
isCloseButtonVisible: true, isCloseButtonVisible: true,
); );
} }
onLocationDeniedForever?.call();
return;
} }
Position? currentLocation = await Geolocator.getLastKnownPosition(); Position? currentLocation = await Geolocator.getLastKnownPosition();
@ -209,7 +253,10 @@ class LocationUtils {
appState.userLong = locationData.longitude; 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 { try {
var location = Location(); var location = Location();
HmsLocation.FusedLocationProviderClient locationService = HmsLocation.FusedLocationProviderClient()..initFusedLocationService(); HmsLocation.FusedLocationProviderClient locationService = HmsLocation.FusedLocationProviderClient()..initFusedLocationService();
@ -230,7 +277,32 @@ class LocationUtils {
LocationPermission permissionGranted = await Geolocator.checkPermission(); LocationPermission permissionGranted = await Geolocator.checkPermission();
if (permissionGranted == LocationPermission.denied) { if (permissionGranted == LocationPermission.denied) {
permissionGranted = await Geolocator.requestPermission(); 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(); appState.resetLocation();
onFailure?.call(); onFailure?.call();
return; 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 /// 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( return Image.network(
url, url,
width: width, width: width,
height: height, height: height,
fit: fit, 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);
},
); );
} }

@ -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/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.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/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class EmergencyServicesRepo { abstract class EmergencyServicesRepo {
Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures(); Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures();
Future<Either<Failure, GenericApiModel<List<ProjectAvgERWaitingTime>>>> getNearestEr({int? id, int? projectID});
} }
class EmergencyServicesRepoImp implements EmergencyServicesRepo { class EmergencyServicesRepoImp implements EmergencyServicesRepo {
@ -16,6 +19,42 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
EmergencyServicesRepoImp({required this.loggerService, required this.apiClient}); EmergencyServicesRepoImp({required this.loggerService, required this.apiClient});
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()));
}
}
@override @override
Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures() async { Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures() async {
Map<String, dynamic> mapDevice = {}; Map<String, dynamic> mapDevice = {};

@ -1,12 +1,28 @@
import 'dart:async';
import 'package:flutter/material.dart'; 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/emergency_services_repo.dart';
import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/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/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 { class EmergencyServicesViewModel extends ChangeNotifier {
EmergencyServicesRepo emergencyServicesRepo; EmergencyServicesRepo emergencyServicesRepo;
ErrorHandlerService errorHandlerService; ErrorHandlerService errorHandlerService;
final NavigationService navServices;
final LocationUtils? locationUtils;
final AppState appState;
bool isERListLoading = false;
List<ProjectAvgERWaitingTime> nearestERList = [];
List<ProjectAvgERWaitingTime> nearestERFilteredList = [];
List<RRTProceduresResponseModel> RRTProceduresList = []; List<RRTProceduresResponseModel> RRTProceduresList = [];
late RRTProceduresResponseModel selectedRRTProcedure; late RRTProceduresResponseModel selectedRRTProcedure;
@ -16,7 +32,13 @@ class EmergencyServicesViewModel extends ChangeNotifier {
notifyListeners(); 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<void> getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async {
RRTProceduresList.clear(); 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<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 emergencyServicesRepo.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;
// }
//}

@ -22,9 +22,8 @@ import 'package:provider/provider.dart';
class EmergencyServicesPage extends StatelessWidget { class EmergencyServicesPage extends StatelessWidget {
EmergencyServicesPage({super.key}); EmergencyServicesPage({super.key});
LocationUtils? locationUtils;
late EmergencyServicesViewModel emergencyServicesViewModel; late EmergencyServicesViewModel emergencyServicesViewModel;
LocationUtils? locationUtils;
@override @override
Widget build(BuildContext context) { 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), Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h),
], ],
).onPress(() { ).onPress(() {
locationUtils!.getLocation(onSuccess: (position) { context.read<EmergencyServicesViewModel>().navigateToNearestERPage();
Navigator.of(context).push(
CustomPageRoute(
page: NearestErPage(),
),
);
});
}), }),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),

@ -1,5 +1,21 @@
import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension;
import 'package:flutter/material.dart'; 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/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 { class NearestErPage extends StatefulWidget {
const NearestErPage({super.key}); const NearestErPage({super.key});
@ -9,11 +25,80 @@ class NearestErPage extends StatefulWidget {
} }
class _NearestErPageState extends State<NearestErPage> { class _NearestErPageState extends State<NearestErPage> {
final TextEditingController searchText = TextEditingController();
final Debouncer debouncer = Debouncer(milliseconds: 500);
@override
void dispose() {
debouncer.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CollapsingListView( return CollapsingListView(
title: "Nearest ER", title: "Nearest ER".needTranslation,
child: Container(), 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: [ children: [
if (icon != null) if (icon != null)
Padding( 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), 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), padding: EdgeInsets.only(top: 0),
child: Text( child: Text(
text, text,
@ -83,6 +86,7 @@ class CustomButton extends StatelessWidget {
), ),
), ),
), ),
),
], ],
), ),
)); ));

Loading…
Cancel
Save