nearest er module added
#84
Merged
Haroon6138
merged 3 commits from nearest_er into master 3 weeks ago
@ -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 |
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
// }
|
||||
//}
|
||||
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue