Added possible conditions screen

pull/113/head
faizatflutter 2 months ago
parent 757ace2ba1
commit acb6d31ff3

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.75 15C12.75 14.5858 12.4142 14.25 12 14.25C11.5858 14.25 11.25 14.5858 11.25 15V18C11.25 18.4142 11.5858 18.75 12 18.75C12.4142 18.75 12.75 18.4142 12.75 18V15Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C11.1339 1.25 10.3597 1.52688 9.52381 1.99594C8.7139 2.45043 7.78586 3.12145 6.61887 3.96524L5.10984 5.05632C4.1733 5.73346 3.42528 6.27429 2.86109 6.77487C2.27855 7.29173 1.84274 7.807 1.5663 8.45513C1.28925 9.10469 1.22225 9.77045 1.25941 10.5381C1.29528 11.2789 1.43264 12.1727 1.60393 13.2872L1.91918 15.3387C2.16256 16.9225 2.35635 18.1836 2.64105 19.1662C2.93544 20.1821 3.35016 20.9887 4.0914 21.6052C4.82957 22.2192 5.7089 22.4926 6.78306 22.6231C7.828 22.75 9.14615 22.75 10.8111 22.75H13.1889C14.8539 22.75 16.172 22.75 17.2169 22.6231C18.2911 22.4926 19.1704 22.2192 19.9086 21.6052C20.6499 20.9887 21.0646 20.1821 21.359 19.1662C21.6437 18.1837 21.8374 16.9225 22.0808 15.3387L22.3961 13.2871C22.5674 12.1726 22.7047 11.2789 22.7406 10.5381C22.7778 9.77045 22.7108 9.10469 22.4337 8.45513C22.1573 7.807 21.7215 7.29173 21.1389 6.77487C20.5747 6.2743 19.8267 5.73347 18.8902 5.05633L17.3811 3.96525C16.2142 3.12146 15.2861 2.45043 14.4762 1.99594C13.6403 1.52688 12.8661 1.25 12 1.25ZM7.46043 5.20777C8.67318 4.33092 9.53198 3.7114 10.2579 3.30406C10.9664 2.90645 11.483 2.75 12 2.75C12.517 2.75 13.0336 2.90645 13.7421 3.30406C14.468 3.7114 15.3268 4.33092 16.5396 5.20777L17.9794 6.24884C18.9551 6.95427 19.6418 7.45189 20.1434 7.8969C20.633 8.33129 20.8961 8.6735 21.054 9.04362C21.2112 9.41232 21.2731 9.83 21.2423 10.4655C21.2107 11.119 21.0862 11.9356 20.9073 13.0995L20.6063 15.0585C20.353 16.7066 20.1726 17.8709 19.9182 18.7487C19.6704 19.6038 19.3761 20.0971 18.9494 20.452C18.5197 20.8094 17.9613 21.0217 17.0361 21.134C16.0921 21.2487 14.865 21.25 13.1388 21.25H10.8612C9.13497 21.25 7.90793 21.2487 6.96392 21.134C6.03868 21.0217 5.48034 20.8094 5.0506 20.452C4.62394 20.0971 4.32959 19.6038 4.08178 18.7487C3.82742 17.8709 3.647 16.7066 3.39373 15.0585L3.09269 13.0995C2.91382 11.9356 2.7893 11.119 2.75766 10.4655C2.72689 9.83 2.78878 9.41232 2.94604 9.04362C3.1039 8.6735 3.36702 8.33129 3.85661 7.8969C4.35817 7.45189 5.04492 6.95427 6.02058 6.24884L7.46043 5.20777Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1661 3.95442C10.7553 3.82074 11.369 3.75 12.0002 3.75C16.5565 3.75 20.2502 7.44365 20.2502 12C20.2502 14.3316 19.2833 16.4376 17.7269 17.9387V16C17.7269 15.5858 17.3911 15.25 16.9769 15.25C16.5626 15.25 16.2269 15.5858 16.2269 16V19.5C16.2269 19.9142 16.5626 20.25 16.9769 20.25H20.5002C20.9144 20.25 21.2502 19.9142 21.2502 19.5C21.2502 19.0858 20.9144 18.75 20.5002 18.75H19.0357C20.7162 16.9987 21.7502 14.6202 21.7502 12C21.7502 6.61522 17.3849 2.25 12.0002 2.25C11.2566 2.25 10.5315 2.33338 9.8342 2.49161C9.43025 2.58327 9.1771 2.98504 9.26876 3.38898C9.36042 3.79293 9.76218 4.04608 10.1661 3.95442Z" fill="#2B353E"/>
<path d="M3.5 3.76562C3.08579 3.76562 2.75 4.10141 2.75 4.51562C2.75 4.92984 3.08579 5.26562 3.5 5.26562H4.94958C3.27784 7.01543 2.25 9.38763 2.25 12.0001C2.25 17.3849 6.61522 21.7501 12 21.7501C12.7436 21.7501 13.4687 21.6667 14.166 21.5085C14.5699 21.4168 14.8231 21.015 14.7314 20.6111C14.6397 20.2071 14.238 19.954 13.834 20.0457C13.2449 20.1793 12.6312 20.2501 12 20.2501C7.44365 20.2501 3.75 16.5564 3.75 12.0001C3.75 9.68013 4.70717 7.58354 6.25 6.0839L6.25 8.00008C6.25 8.41429 6.58579 8.75008 7 8.75008C7.41421 8.75008 7.75 8.41429 7.75 8.00008L7.75 4.51562C7.75 4.10141 7.41421 3.76562 7 3.76562L3.5 3.76562Z" fill="#2B353E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -192,6 +192,8 @@ class AppAssets {
// Symptoms Checker
static const String bodyIcon = '$svgBasePath/body_icon.svg';
static const String rotateIcon = '$svgBasePath/rotate_icon.svg';
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';

@ -241,3 +241,7 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
}
}
}
// SymptomsChecker
enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency }

@ -3,12 +3,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices;
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.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';
@ -16,29 +14,24 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/RRTRequestModel.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/RRTRequestModel.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart'
show PatientERTransportationMethod;
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart';
import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart' show PlaceDetails;
import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_map_screen.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/terms_and_condition.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart';
@ -85,7 +78,6 @@ class EmergencyServicesViewModel extends ChangeNotifier {
List<ProjectAvgERWaitingTime> nearestERList = [];
List<ProjectAvgERWaitingTime> nearestERFilteredList = [];
List<HospitalsModel>? hospitalList;
List<HospitalsModel>? hmgHospitalList;
List<HospitalsModel>? hmcHospitalList;
@ -104,7 +96,6 @@ class EmergencyServicesViewModel extends ChangeNotifier {
AmbulanceCallingPlace callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL;
AmbulanceDirection ambulanceDirection = AmbulanceDirection.ONE_WAY;
bool patientHasAdvanceERBalance = false;
bool isERBookAppointment = false;
late EROnlineCheckInPaymentDetailsResponse erOnlineCheckInPaymentDetailsResponse;
@ -118,14 +109,11 @@ class EmergencyServicesViewModel extends ChangeNotifier {
RRTServiceData? ordersRRT;
TextEditingController rrtNotes = TextEditingController();
List allOrders = [];
List orderDisplayList = [];
bool historyLoading= false;
bool historyLoading = false;
OrderDislpay currentlyDisplayedOrder = OrderDislpay.ALL;
setSelectedRRTProcedure(RRTProceduresResponseModel procedure) {
selectedRRTProcedure = procedure;
notifyListeners();
@ -161,7 +149,6 @@ class EmergencyServicesViewModel extends ChangeNotifier {
String? termsAndConditions;
Future<void> getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async {
print("the app state is ${appState.isAuthenticated}");
if (!appState.isAuthenticated) {
dialogService.showErrorBottomSheet(
@ -797,8 +784,8 @@ class EmergencyServicesViewModel extends ChangeNotifier {
historyLoading = false;
ambulanceOrders = apiResponse.data;
allOrders.clear();
allOrders.addAll(ambulanceOrders??[]);
allOrders.addAll(ordersRRT?.completedOrders??[]);
allOrders.addAll(ambulanceOrders ?? []);
allOrders.addAll(ordersRRT?.completedOrders ?? []);
changeOrderDisplayItems(OrderDislpay.ALL);
notifyListeners();
},
@ -889,8 +876,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
});
}
RRTRequestModel createRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction){
RRTRequestModel createRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) {
AuthenticatedUser? user = appState.getAuthenticatedUser();
if (user == null) throw Exception("Authentication Required to Continue");
@ -898,7 +884,6 @@ class EmergencyServicesViewModel extends ChangeNotifier {
Procedures procedures = new Procedures();
rrtRequestModel.procedures = [];
procedures.serviceID = selectedRRTProcedure?.procedureID;
rrtRequestModel.latitude = ((result?.geometry.location.lat) ?? place?.lat);
@ -920,7 +905,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
notifyListeners();
}
FutureOr<void> submitRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async {
FutureOr<void> submitRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async {
RRTRequestModel request = createRRTRequest(result, place, placePrediction);
navServices.push(CustomPageRoute(page: RequestingServicesPage()));
@ -929,9 +914,9 @@ class EmergencyServicesViewModel extends ChangeNotifier {
navServices.pushAndRemoveUntil(
CustomPageRoute(
page: TrackingScreen(
isRRTOrder: true,
state: OrderTrackingState.failed,
)),
isRRTOrder: true,
state: OrderTrackingState.failed,
)),
ModalRoute.withName("/EmergencyServicesPage"));
}, (success) {
getRRTOrders(shouldNavigateToTrackingScreen: true);
@ -939,7 +924,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
}
Future<void> getRRTOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async {
if(shouldNavigateToTrackingScreen == false && showLoader ) {
if (shouldNavigateToTrackingScreen == false && showLoader) {
LoaderBottomSheet.showLoader(loadingText: "Fetching Orders");
}
historyLoading = true;
@ -947,39 +932,40 @@ class EmergencyServicesViewModel extends ChangeNotifier {
int? id = appState.getAuthenticatedUser()?.patientId;
var response = await emergencyServicesRepo.getRRTOrders(id: id);
if(shouldNavigateToTrackingScreen == false && showLoader ) {
LoaderBottomSheet.hideLoader();}
if (shouldNavigateToTrackingScreen == false && showLoader) {
LoaderBottomSheet.hideLoader();
}
response.fold(
(failure) async {
historyLoading = false;
notifyListeners();
(failure) async {
historyLoading = false;
notifyListeners();
if (shouldNavigateToTrackingScreen) {
navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(isRRTOrder: true,state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage"));
navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(isRRTOrder: true, state: OrderTrackingState.waitingForCall)),
ModalRoute.withName("/EmergencyServicesPage"));
}
},
(apiResponse) {
(apiResponse) {
if (shouldNavigateToTrackingScreen) {
navServices.pushAndRemoveUntil(
CustomPageRoute(
page: TrackingScreen(
state: OrderTrackingState.waitingForCall,
isRRTOrder: true,
rrtOrder: apiResponse.data?.pendingOrders.first,
)),
state: OrderTrackingState.waitingForCall,
isRRTOrder: true,
rrtOrder: apiResponse.data?.pendingOrders.first,
)),
ModalRoute.withName("/EmergencyServicesPage"));
}
historyLoading = false;
ordersRRT = apiResponse.data;
allOrders.clear();
allOrders.addAll(ambulanceOrders??[]);
allOrders.addAll(ordersRRT?.completedOrders??[]);
allOrders.addAll(ambulanceOrders ?? []);
allOrders.addAll(ordersRRT?.completedOrders ?? []);
changeOrderDisplayItems(OrderDislpay.ALL);
notifyListeners();
},
);
}
FutureOr<void> cancelRRTOrder(int? orderID, {bool shouldPop = false}) async {
dialogService.showCommonBottomSheetWithoutH(
message: "Do you want to cancel the request".needTranslation,
@ -998,52 +984,54 @@ class EmergencyServicesViewModel extends ChangeNotifier {
});
}
void changeOrderDisplayItems(OrderDislpay currentlyDisplayedOrder){
void changeOrderDisplayItems(OrderDislpay currentlyDisplayedOrder) {
this.currentlyDisplayedOrder = currentlyDisplayedOrder;
switch(currentlyDisplayedOrder){
switch (currentlyDisplayedOrder) {
case OrderDislpay.ALL:
orderDisplayList = allOrders;
break;
orderDisplayList = allOrders;
break;
case OrderDislpay.RRT:
orderDisplayList = ordersRRT?.completedOrders ?? [];
break;
case OrderDislpay.AMBULANCE:
orderDisplayList = ambulanceOrders??[];
orderDisplayList = ambulanceOrders ?? [];
break;
}
notifyListeners();
}
void openRRT(){
void openRRT() {
print("the app state is ${appState.isAuthenticated}");
if (appState.isAuthenticated) {
if(agreedToTermsAndCondition == false){
dialogService.showErrorBottomSheet(message: "You Need To Agree To Terms And Conditions".needTranslation, onOkPressed: (){
if(navServices.context == null ) return;
showCommonBottomSheetWithoutHeight(
navServices.context!,
padding: EdgeInsets.only(top: 24.h),
titleWidget: Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_back,
iconColor: Color(0xff2B353E),
fit: BoxFit.contain,
),
).onPress(() {
navServices.pop();
}),
// title: "Rapid Response Team (RRT)".needTranslation,
child: RrtRequestTypeSelect(),
isFullScreen: false,
isCloseButtonVisible: true,
hasBottomPadding: false,
backgroundColor: AppColors.bottomSheetBgColor,
callBackFunc: () {
navServices.pop();
},
);
});
if (agreedToTermsAndCondition == false) {
dialogService.showErrorBottomSheet(
message: "You Need To Agree To Terms And Conditions".needTranslation,
onOkPressed: () {
if (navServices.context == null) return;
showCommonBottomSheetWithoutHeight(
navServices.context!,
padding: EdgeInsets.only(top: 24.h),
titleWidget: Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_back,
iconColor: Color(0xff2B353E),
fit: BoxFit.contain,
),
).onPress(() {
navServices.pop();
}),
// title: "Rapid Response Team (RRT)".needTranslation,
child: RrtRequestTypeSelect(),
isFullScreen: false,
isCloseButtonVisible: true,
hasBottomPadding: false,
backgroundColor: AppColors.bottomSheetBgColor,
callBackFunc: () {
navServices.pop();
},
);
});
return;
}
placeValueInController();
@ -1051,7 +1039,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
isShowConfirmDialog: true,
onSuccess: (position) async {
updateBottomSheetState(BottomSheetType.FIXED);
bool result = await navServices.push(
bool result = await navServices.push(
CustomPageRoute(
page: MapUtilityScreen(
confirmButtonString: "Submit Request ".needTranslation,
@ -1061,16 +1049,15 @@ class EmergencyServicesViewModel extends ChangeNotifier {
),
direction: AxisDirection.down),
);
if(result){
LocationViewModel locationViewModel = getIt.get<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
submitRRTRequest(response?.results.first, placeDetails, placePrediction);
}
if (result) {
LocationViewModel locationViewModel = getIt.get<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
submitRRTRequest(response?.results.first, placeDetails, placePrediction);
}
});
} else{
} else {
dialogService.showErrorBottomSheet(
message: "You Need To Login First To Continue".needTranslation,
onOkPressed: () {
@ -1079,24 +1066,22 @@ class EmergencyServicesViewModel extends ChangeNotifier {
});
}
}
clearRRTData(){
clearRRTData() {
selectedRRTProcedure = null;
}
FutureOr<void> getTermsAndConditions() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Terms And Conditions".needTranslation);
var response = await emergencyServicesRepo.getTermsAndCondition();
LoaderBottomSheet.hideLoader();
response.fold((failure)=>errorHandlerService.handleError(failure: failure),(success){
response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) {
termsAndConditions = success.data;
print("the response terms are $termsAndConditions");
notifyListeners();
navServices.push(
CustomPageRoute(
page: TermsAndCondition(termsAndCondition:success.data??""), direction: AxisDirection.down),
CustomPageRoute(page: TermsAndCondition(termsAndCondition: success.data ?? ""), direction: AxisDirection.down),
);
});
}
}

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
class ConditionsModel {
final IconData icon;
final String title;
final int percentage;
final String tagText;
final String clinic;
final List<String> symptoms;
final String description;
final String? monitorNote;
final String? appointmentLabel;
final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum;
ConditionsModel({
required this.icon,
required this.title,
required this.percentage,
required this.tagText,
required this.clinic,
required this.symptoms,
required this.description,
required this.possibleConditionsSeverityEnum,
this.monitorNote,
this.appointmentLabel,
});
}
List<ConditionsModel> dummyConditions = [
ConditionsModel(
icon: Icons.psychology_alt,
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
title: "Migraine",
percentage: 87,
tagText: "Seek Medical Advice",
clinic: "Internal Medicine Clinic",
symptoms: ["Headache", "Nausea", "Sensitivity to light"],
description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
appointmentLabel: "Book Appointment",
),
ConditionsModel(
icon: Icons.deblur,
title: "Tension Headache",
percentage: 37,
tagText: "Monitor",
monitorNote: "No need to visit doctor",
clinic: "GP Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
),
ConditionsModel(
icon: Icons.medication_liquid,
title: "Meningitis",
percentage: 28,
tagText: "Emergency",
clinic: "Neurology Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
),
ConditionsModel(
icon: Icons.psychology_alt,
title: "Migraine",
percentage: 87,
tagText: "Seek Medical Advice",
clinic: "Internal Medicine Clinic",
symptoms: ["Headache", "Nausea", "Sensitivity to light"],
description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
),
ConditionsModel(
icon: Icons.deblur,
title: "Tension Headache",
percentage: 37,
tagText: "Monitor",
monitorNote: "No need to visit doctor",
clinic: "GP Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
),
ConditionsModel(
icon: Icons.medication_liquid,
title: "Meningitis",
percentage: 28,
tagText: "Emergency",
clinic: "Neurology Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
),
];

File diff suppressed because one or more lines are too long

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.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/presentation/symptoms_checker/symptoms_checker_view_model.dart';
@ -47,6 +48,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
backgroundColor: AppColors.successColor,
),
);
context.navigateWithName(AppRoutes.possibleConditionsScreen);
}
@override
@ -116,7 +118,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) {
return Padding(
padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 45.h),
padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 600),
transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation),
@ -129,6 +131,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
selectedOrganIds: viewModel.selectedOrganIds,
onOrganTap: viewModel.toggleOrganSelection,
isBodyHidden: viewModel.isBodyHidden,
tooltipOrganId: viewModel.tooltipOrganId,
),
),
);

@ -0,0 +1,177 @@
import 'package:easy_localization/easy_localization.dart';
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.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/symptoms_checker/models/conditions_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/services/navigation_service.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/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class PossibleConditionsScreen extends StatelessWidget {
const PossibleConditionsScreen({super.key});
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(16.w),
itemCount: 10,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 80.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.r),
),
),
);
},
);
}
Widget _buildPredictionsList(List<ConditionsModel> conditions) {
if (conditions.isEmpty) {
return Center(
child: Padding(
padding: EdgeInsets.all(24.h),
child: Text(
'No Predictions available'.needTranslation,
style: TextStyle(
fontSize: 16.h,
color: AppColors.greyTextColor,
),
),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
ListView.separated(
padding: EdgeInsets.zero,
itemCount: conditions.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
final conditionModel = conditions[index];
return ConditionCard(
icon: conditionModel.icon,
title: conditionModel.title,
percentage: conditionModel.percentage,
tagText: conditionModel.tagText,
clinic: conditionModel.clinic,
symptoms: conditionModel.symptoms,
description: conditionModel.description,
possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum,
onActionPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('We are not available for a week. May you Rest In Peace :('),
backgroundColor: AppColors.primaryRedColor,
),
);
},
);
},
).paddingSymmetrical(24.w, 0),
SizedBox(height: 24.h),
],
);
}
_buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to restart the organ selection?".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
_restartOrganSelection(BuildContext context) async {
final symptomsCheckerVm = context.read<SymptomsCheckerViewModel>();
symptomsCheckerVm.reset();
context.pop();
await Future.delayed(Duration(seconds: 1)).whenComplete(() => context.pop());
}
_navigateToLandingPage() {
NavigationService navigationService = getIt.get<NavigationService>();
navigationService.replaceAllRoutesAndNavigateToLanding();
}
_buildTrailingSection(BuildContext context) {
return Row(
children: [
Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
child: Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.refreshIcon,
height: 20.h,
width: 20.w,
iconColor: AppColors.textColor,
),
),
).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _restartOrganSelection(context))),
Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
child: Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.homeBorderedIcon,
height: 20.h,
width: 20.w,
iconColor: AppColors.textColor,
),
),
).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _navigateToLandingPage())),
],
).paddingSymmetrical(20.w, 0);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "Possible Conditions".needTranslation,
trailing: _buildTrailingSection(context),
child: Consumer<SymptomsCheckerViewModel>(
builder: (context, symptomsCheckerViewModel, child) {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) {
return _buildLoadingShimmer();
}
return _buildPredictionsList(dummyConditions);
},
),
),
);
}
}

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
@ -10,14 +12,22 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final Set<String> _selectedOrganIds = {};
bool _isBottomSheetExpanded = false;
// Tooltip state
String? _tooltipOrganId;
Timer? _tooltipTimer;
// Getters
bool isPossibleConditionsLoading = false;
BodyView get currentView => _currentView;
Set<String> get selectedOrganIds => _selectedOrganIds;
bool get isBottomSheetExpanded => _isBottomSheetExpanded;
String? get tooltipOrganId => _tooltipOrganId;
/// Get organs for current view
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
@ -56,6 +66,33 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
} else {
_selectedOrganIds.add(organId);
}
// Show tooltip
_showTooltip(organId);
notifyListeners();
}
/// Show tooltip for an organ
void _showTooltip(String organId) {
// Cancel any existing timer
_tooltipTimer?.cancel();
// Set the tooltip organ
_tooltipOrganId = organId;
notifyListeners();
// Hide tooltip after 2 seconds
_tooltipTimer = Timer(const Duration(seconds: 1), () {
_tooltipOrganId = null;
notifyListeners();
});
}
/// Hide tooltip immediately
void hideTooltip() {
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
@ -103,6 +140,15 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_currentView = BodyView.front;
_selectedOrganIds.clear();
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
@override
void dispose() {
_tooltipTimer?.cancel();
super.dispose();
}
}

@ -0,0 +1,199 @@
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/enums.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/presentation/symptoms_checker/widgets/custom_progress_bar.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';
class ConditionCard extends StatelessWidget {
final IconData icon;
final String title;
final int percentage;
final String tagText;
final String clinic;
final List<String> symptoms;
final String description;
final String? monitorNote;
final String? appointmentLabel;
final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum;
final VoidCallback? onActionPressed;
const ConditionCard({
super.key,
required this.icon,
required this.title,
required this.percentage,
required this.tagText,
required this.clinic,
required this.symptoms,
required this.description,
this.monitorNote,
this.appointmentLabel,
required this.possibleConditionsSeverityEnum,
this.onActionPressed,
});
Color getChipColorBySeverityEnum(PossibleConditionsSeverityEnum possibleConditionsSeverityEnum) {
switch (possibleConditionsSeverityEnum) {
case PossibleConditionsSeverityEnum.seekMedicalAdvice:
return AppColors.chipColorSeekMedicalAdvice;
case PossibleConditionsSeverityEnum.monitorOnly:
return AppColors.chipColorMonitor;
case PossibleConditionsSeverityEnum.emergency:
return AppColors.chipColorEmergency;
}
}
Color getChipTextColorBySeverityEnum(PossibleConditionsSeverityEnum possibleConditionsSeverityEnum) {
switch (possibleConditionsSeverityEnum) {
case PossibleConditionsSeverityEnum.seekMedicalAdvice:
return AppColors.chipTextColorSeekMedicalAdvice;
case PossibleConditionsSeverityEnum.monitorOnly:
return AppColors.chipColorMonitor;
case PossibleConditionsSeverityEnum.emergency:
return AppColors.chipColorEmergency;
}
}
_buildSymptomsRow() {
return Wrap(
spacing: 6.w,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (int i = 0; i < symptoms.length; i++) ...[
Text(symptoms[i], style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)),
if (i != symptoms.length - 1)
Padding(
padding: EdgeInsets.symmetric(horizontal: 2.w),
child: Icon(Icons.circle, size: 7, color: AppColors.textColor),
),
],
],
);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48.w,
height: 48.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2),
borderRadius: 12.r,
hasShadow: false,
),
child: Icon(icon, color: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum), size: 24.f),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
overflow: TextOverflow.ellipsis,
),
),
AppCustomChipWidget(
labelText: tagText,
backgroundColor: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2),
textColor: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum),
),
],
),
CustomRoundedProgressBar(
percentage: percentage,
height: 6.h,
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum),
backgroundColor: AppColors.scaffoldBgColor,
titleWidget: Row(
children: [
Text(
"$percentage%",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.f,
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum),
),
),
],
).paddingSymmetrical(0, 4.h),
),
],
),
),
],
).paddingAll(16.w),
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
clinic,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14.f,
color: AppColors.textColor,
),
),
_buildSymptomsRow(),
SizedBox(height: 16.h),
Text("Description".needTranslation, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.f, color: AppColors.textColor)),
SizedBox(height: 2.h),
Text(description, style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)),
if (possibleConditionsSeverityEnum == PossibleConditionsSeverityEnum.emergency)
CustomButton(
text: appointmentLabel ?? "Book Appointment".needTranslation,
onPressed: () {
if (onActionPressed != null) {
onActionPressed!();
}
},
backgroundColor: AppColors.lightRedButtonColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 48.h,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 18.h,
).paddingOnly(top: 16.w),
if (monitorNote != null)
Container(
margin: EdgeInsets.only(top: 12.h),
child: AppCustomChipWidget(
labelText: monitorNote!,
backgroundColor: AppColors.whiteColor,
textColor: AppColors.textColor,
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
),
),
],
).paddingAll(16.w),
],
),
);
}
}

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
class CustomRoundedProgressBar extends StatelessWidget {
final int percentage;
final Color color;
final Color backgroundColor;
final double? height;
final Widget? titleWidget;
const CustomRoundedProgressBar({
super.key,
this.titleWidget,
required this.percentage,
required this.color,
required this.backgroundColor,
this.height,
});
@override
Widget build(BuildContext context) {
final h = height ?? 8.h;
return Column(
children: [
if (titleWidget != null) ...[
titleWidget!,
],
LayoutBuilder(
builder: (context, constraints) {
double radius = h / 2;
return Container(
height: h,
width: constraints.maxWidth,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(radius),
),
child: Align(
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: (percentage.clamp(0, 100)) / 100,
child: Container(
height: height,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.horizontal(
left: Radius.circular(radius),
right: Radius.circular(radius),
),
),
),
),
),
);
},
),
],
);
}
}

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/organ_dot_widget.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart';
class InteractiveBodyWidget extends StatefulWidget {
final String bodyImageAsset;
@ -11,6 +12,7 @@ class InteractiveBodyWidget extends StatefulWidget {
final Set<String> selectedOrganIds;
final bool isBodyHidden;
final Function(String organId) onOrganTap;
final String? tooltipOrganId;
const InteractiveBodyWidget({
super.key,
@ -19,6 +21,7 @@ class InteractiveBodyWidget extends StatefulWidget {
required this.selectedOrganIds,
required this.onOrganTap,
this.isBodyHidden = false,
this.tooltipOrganId,
});
@override
@ -77,13 +80,13 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
transformationController: _transformationController,
minScale: 0.5,
maxScale: 4.0,
boundaryMargin: const EdgeInsets.all(20),
clipBehavior: Clip.none,
child: AspectRatio(
aspectRatio: _imageAspectRatio!,
child: LayoutBuilder(
builder: (context, imageConstraints) {
return Stack(
clipBehavior: Clip.none,
children: [
// Body image
Positioned.fill(
@ -108,6 +111,18 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
child: OrganDot(isSelected: isSelected, onTap: () => widget.onOrganTap(organ.id), size: dotSize),
);
}),
// Tooltip
if (widget.tooltipOrganId != null)
...widget.organs.where((organ) => organ.id == widget.tooltipOrganId).map((organ) {
final centerX = organ.position.x * imageConstraints.maxWidth;
final centerY = organ.position.y * imageConstraints.maxHeight;
return OrganTooltipWidget(
organName: organ.name,
position: Offset(centerX, centerY),
);
}),
],
);
},

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class OrganTooltipWidget extends StatelessWidget {
final String organName;
final Offset position;
const OrganTooltipWidget({super.key, required this.organName, required this.position});
@override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: FractionalTranslation(
translation: const Offset(-0.5, -1.2), // Center horizontally, position above the dot
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 8.h),
child: Text(
organName,
style: TextStyle(
color: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}

@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
class AppRoutes {
@ -23,6 +24,7 @@ class AppRoutes {
// Symptoms Checker
static const String organSelectorPage = '/organSelectorPage';
static const String possibleConditionsScreen = '/possibleConditionsScreen';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
@ -34,6 +36,7 @@ class AppRoutes {
eReferralPage: (context) => EReferralPage(),
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(),
organSelectorPage: (context) => OrganSelectorPage()
organSelectorPage: (context) => OrganSelectorPage(),
possibleConditionsScreen: (context) => PossibleConditionsScreen()
};
}

@ -82,4 +82,11 @@ class AppColors {
static const Color infoBannerBorderColor = Color(0xFFFFE5B4);
static const Color infoBannerIconColor = Color(0xFFCC9B14);
static const Color infoBannerTextColor = Color(0xFF856404);
// SymptomsChecker
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15);
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103);
static const Color chipColorMonitor = Color(0xFF18C273);
static const Color chipColorEmergency = Color(0xFFED1C2B);
}

Loading…
Cancel
Save