diff --git a/assets/images/svg/home_bordered.svg b/assets/images/svg/home_bordered.svg
new file mode 100644
index 0000000..b9f6291
--- /dev/null
+++ b/assets/images/svg/home_bordered.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/refresh.svg b/assets/images/svg/refresh.svg
new file mode 100644
index 0000000..6a84970
--- /dev/null
+++ b/assets/images/svg/refresh.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index eb6f74c..ab2bfb2 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -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';
diff --git a/lib/core/enums.dart b/lib/core/enums.dart
index e318401..6754e8c 100644
--- a/lib/core/enums.dart
+++ b/lib/core/enums.dart
@@ -241,3 +241,7 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
}
}
}
+
+// SymptomsChecker
+
+enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency }
diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart
index ad72ef4..0f264c5 100644
--- a/lib/features/emergency_services/emergency_services_view_model.dart
+++ b/lib/features/emergency_services/emergency_services_view_model.dart
@@ -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 nearestERList = [];
List nearestERFilteredList = [];
-
List? hospitalList;
List? hmgHospitalList;
List? 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 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 submitRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async {
+ FutureOr 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 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 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();
- GeocodeResponse? response = locationViewModel.geocodeResponse;
- PlaceDetails? placeDetails = locationViewModel.placeDetails;
- PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
- submitRRTRequest(response?.results.first, placeDetails, placePrediction);
- }
-
+ if (result) {
+ LocationViewModel locationViewModel = getIt.get();
+ 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 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),
);
});
}
-
}
diff --git a/lib/features/symptoms_checker/models/conditions_model.dart b/lib/features/symptoms_checker/models/conditions_model.dart
new file mode 100644
index 0000000..774d8c3
--- /dev/null
+++ b/lib/features/symptoms_checker/models/conditions_model.dart
@@ -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 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 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,
+ ),
+];
diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart
index 4ffd979..0efa5da 100644
--- a/lib/presentation/lab/lab_orders_page.dart
+++ b/lib/presentation/lab/lab_orders_page.dart
@@ -1 +1 @@
-import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/enums.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/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart';
import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import '../../widgets/appbar/collapsing_list_view.dart';
class LabOrdersPage extends StatefulWidget {
const LabOrdersPage({super.key});
@override
State createState() => _LabOrdersPageState();
}
class _LabOrdersPageState extends State {
late LabViewModel labProvider;
late DateRangeSelectorRangeViewModel rangeViewModel;
late AppState _appState;
List?> labSuggestions = [];
int? expandedIndex;
String? selectedFilterText = '';
int activeIndex = 0;
@override
void initState() {
scheduleMicrotask(() {
labProvider.initLabProvider();
});
super.initState();
}
@override
Widget build(BuildContext context) {
labProvider = Provider.of(context, listen: false);
rangeViewModel = Provider.of(context);
_appState = getIt();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.labResults.tr(),
search: () async {
final lavVM = Provider.of(context, listen: false);
if (lavVM.isLabOrdersLoading) {
return;
} else {
String? value = await Navigator.of(context).push(
CustomPageRoute(
page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions),
fullScreenDialog: true,
direction: AxisDirection.down,
),
);
if (value != null) {
selectedFilterText = value;
lavVM.filterLabReports(value);
}
}
},
child: SingleChildScrollView(
padding: EdgeInsets.all(24.h),
physics: NeverScrollableScrollPhysics(),
child: Consumer(
builder: (context, model, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, "By Visit".needTranslation),
CustomTabBarModel(null, "By Test".needTranslation),
// CustomTabBarModel(null, "Completed".needTranslation),
],
onTabChange: (index) {
activeIndex = index;
setState(() {});
},
),
SizedBox(height: 8.h),
selectedFilterText!.isNotEmpty
? CustomChipWidget(
chipText: selectedFilterText!,
chipType: ChipTypeEnum.alert,
isSelected: true,
)
: SizedBox(),
activeIndex == 0
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.isLabOrdersLoading
? 5
: model.patientLabOrders.isNotEmpty
? model.patientLabOrders.length
: 1,
itemBuilder: (context, index) {
final isExpanded = expandedIndex == index;
return model.isLabOrdersLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: model.patientLabOrders.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabResultItemView(
onTap: () {
model.currentlySelectedPatientOrder =
model.patientLabOrders[
index];
labProvider.getPatientLabResultByHospital(model.patientLabOrders[
index]);
labProvider
.getPatientSpecialResult(
model.patientLabOrders[
index]);
Navigator.push(
context,
CustomPageRoute(
page: LabResultByClinic(labOrder: model.patientLabOrders[index]),
));
},
labOrder: model.patientLabOrders[index],
index: index,
isExpanded: isExpanded),
),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation);
},
)
: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.isLabOrdersLoading
? 5
: model.uniqueTests.toList().isNotEmpty
? model.uniqueTests.toList().length
: 1,
itemBuilder: (context, index) {
final isExpanded = expandedIndex == index;
return model.isLabOrdersLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: model.uniqueTests.toList().isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabOrderByTest(
appState: _appState,
onTap: () {
if (model.uniqueTests.toList()[index].model != null) {
rangeViewModel.flush();
model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!,
(_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!));
}
},
tests: model.uniqueTests.toList()[index],
index: index,
isExpanded: isExpanded)),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation);
},
)
],
);
},
),
),
));
}
Color getLabOrderStatusColor(num status) {
switch (status) {
case 44:
return AppColors.warningColorYellow;
case 45:
return AppColors.warningColorYellow;
case 16:
return AppColors.successColor;
case 17:
return AppColors.successColor;
default:
return AppColors.greyColor;
}
}
String getLabOrderStatusText(num status) {
switch (status) {
case 44:
return LocaleKeys.resultsPending.tr(context: context);
case 45:
return LocaleKeys.resultsPending.tr(context: context);
case 16:
return LocaleKeys.resultsAvailable.tr(context: context);
case 17:
return LocaleKeys.resultsAvailable.tr(context: context);
default:
return "";
}
}
getLabSuggestions(LabViewModel model) {
if (model.patientLabOrders.isEmpty) {
return [];
}
return model.patientLabOrders.map((m) => m.testDetails).toList();
}
}
\ No newline at end of file
+import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/enums.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/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart';
import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../../widgets/appbar/collapsing_list_view.dart';
class LabOrdersPage extends StatefulWidget {
const LabOrdersPage({super.key});
@override
State createState() => _LabOrdersPageState();
}
class _LabOrdersPageState extends State {
late LabViewModel labProvider;
late DateRangeSelectorRangeViewModel rangeViewModel;
late AppState _appState;
List?> labSuggestions = [];
int? expandedIndex;
String? selectedFilterText = '';
int activeIndex = 0;
@override
void initState() {
scheduleMicrotask(() {
labProvider.initLabProvider();
});
super.initState();
}
@override
Widget build(BuildContext context) {
labProvider = Provider.of(context, listen: false);
rangeViewModel = Provider.of(context);
_appState = getIt();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.labResults.tr(),
search: () async {
final lavVM = Provider.of(context, listen: false);
if (lavVM.isLabOrdersLoading) {
return;
} else {
String? value = await Navigator.of(context).push(
CustomPageRoute(
page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions),
fullScreenDialog: true,
direction: AxisDirection.down,
),
);
if (value != null) {
selectedFilterText = value;
lavVM.filterLabReports(value);
}
}
},
child: SingleChildScrollView(
padding: EdgeInsets.all(24.h),
physics: NeverScrollableScrollPhysics(),
child: Consumer(
builder: (context, model, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, "By Visit".needTranslation),
CustomTabBarModel(null, "By Test".needTranslation),
// CustomTabBarModel(null, "Completed".needTranslation),
],
onTabChange: (index) {
activeIndex = index;
setState(() {});
},
),
SizedBox(height: 8.h),
selectedFilterText!.isNotEmpty
? CustomChipWidget(
chipText: selectedFilterText!,
chipType: ChipTypeEnum.alert,
isSelected: true,
)
: SizedBox(),
activeIndex == 0
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.isLabOrdersLoading
? 5
: model.patientLabOrders.isNotEmpty
? model.patientLabOrders.length
: 1,
itemBuilder: (context, index) {
final isExpanded = expandedIndex == index;
return model.isLabOrdersLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: model.patientLabOrders.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabResultItemView(
onTap: () {
model.currentlySelectedPatientOrder = model.patientLabOrders[index];
labProvider.getPatientLabResultByHospital(model.patientLabOrders[index]);
labProvider.getPatientSpecialResult(model.patientLabOrders[index]);
Navigator.push(
context,
CustomPageRoute(
page: LabResultByClinic(labOrder: model.patientLabOrders[index]),
));
},
labOrder: model.patientLabOrders[index],
index: index,
isExpanded: isExpanded),
),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation);
},
)
: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.isLabOrdersLoading
? 5
: model.uniqueTests.toList().isNotEmpty
? model.uniqueTests.toList().length
: 1,
itemBuilder: (context, index) {
final isExpanded = expandedIndex == index;
return model.isLabOrdersLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)
: model.uniqueTests.toList().isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabOrderByTest(
appState: _appState,
onTap: () {
if (model.uniqueTests.toList()[index].model != null) {
rangeViewModel.flush();
model.getPatientLabResult(
model.uniqueTests.toList()[index].model!,
model.uniqueTests.toList()[index].description!,
(_appState.isArabic()
? model.uniqueTests.toList()[index].testDescriptionAr!
: model.uniqueTests.toList()[index].testDescriptionEn!));
}
},
tests: model.uniqueTests.toList()[index],
index: index,
isExpanded: isExpanded)),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation);
},
)
],
);
},
),
),
));
}
Color getLabOrderStatusColor(num status) {
switch (status) {
case 44:
return AppColors.warningColorYellow;
case 45:
return AppColors.warningColorYellow;
case 16:
return AppColors.successColor;
case 17:
return AppColors.successColor;
default:
return AppColors.greyColor;
}
}
String getLabOrderStatusText(num status) {
switch (status) {
case 44:
return LocaleKeys.resultsPending.tr(context: context);
case 45:
return LocaleKeys.resultsPending.tr(context: context);
case 16:
return LocaleKeys.resultsAvailable.tr(context: context);
case 17:
return LocaleKeys.resultsAvailable.tr(context: context);
default:
return "";
}
}
getLabSuggestions(LabViewModel model) {
if (model.patientLabOrders.isEmpty) {
return [];
}
return model.patientLabOrders.map((m) => m.testDetails).toList();
}
}
\ No newline at end of file
diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart
index 6b9582c..d945b5b 100644
--- a/lib/presentation/symptoms_checker/organ_selector_screen.dart
+++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart
@@ -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 {
backgroundColor: AppColors.successColor,
),
);
+ context.navigateWithName(AppRoutes.possibleConditionsScreen);
}
@override
@@ -116,7 +118,7 @@ class _OrganSelectorPageState extends State {
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 {
selectedOrganIds: viewModel.selectedOrganIds,
onOrganTap: viewModel.toggleOrganSelection,
isBodyHidden: viewModel.isBodyHidden,
+ tooltipOrganId: viewModel.tooltipOrganId,
),
),
);
diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart
new file mode 100644
index 0000000..0587c32
--- /dev/null
+++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart
@@ -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 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();
+ symptomsCheckerVm.reset();
+ context.pop();
+ await Future.delayed(Duration(seconds: 1)).whenComplete(() => context.pop());
+ }
+
+ _navigateToLandingPage() {
+ NavigationService navigationService = getIt.get();
+ 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(
+ builder: (context, symptomsCheckerViewModel, child) {
+ if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) {
+ return _buildLoadingShimmer();
+ }
+ return _buildPredictionsList(dummyConditions);
+ },
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart b/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart
index 35a23f1..e162b67 100644
--- a/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart
+++ b/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart
@@ -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 _selectedOrganIds = {};
bool _isBottomSheetExpanded = false;
+ // Tooltip state
+ String? _tooltipOrganId;
+ Timer? _tooltipTimer;
+
// Getters
+ bool isPossibleConditionsLoading = false;
+
BodyView get currentView => _currentView;
Set get selectedOrganIds => _selectedOrganIds;
bool get isBottomSheetExpanded => _isBottomSheetExpanded;
+ String? get tooltipOrganId => _tooltipOrganId;
+
/// Get organs for current view
List 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();
+ }
+
}
diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart
new file mode 100644
index 0000000..87a8f3d
--- /dev/null
+++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart
@@ -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 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),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/symptoms_checker/widgets/custom_progress_bar.dart b/lib/presentation/symptoms_checker/widgets/custom_progress_bar.dart
new file mode 100644
index 0000000..5f55395
--- /dev/null
+++ b/lib/presentation/symptoms_checker/widgets/custom_progress_bar.dart
@@ -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),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart
index 33663b5..4c41ddd 100644
--- a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart
+++ b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart
@@ -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 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 {
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 {
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),
+ );
+ }),
],
);
},
diff --git a/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart
new file mode 100644
index 0000000..77e8319
--- /dev/null
+++ b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart
@@ -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,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart
index a144d19..08686d3 100644
--- a/lib/routes/app_routes.dart
+++ b/lib/routes/app_routes.dart
@@ -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 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()
};
}
diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart
index ccdf030..03e889b 100644
--- a/lib/theme/colors.dart
+++ b/lib/theme/colors.dart
@@ -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);
}