diff --git a/assets/images/svg/approximate_ovulation_accordion.svg b/assets/images/svg/approximate_ovulation_accordion.svg
new file mode 100644
index 0000000..a721d61
--- /dev/null
+++ b/assets/images/svg/approximate_ovulation_accordion.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/images/svg/bloodcholestrol.svg b/assets/images/svg/bloodcholestrol.svg
new file mode 100644
index 0000000..8a77bb5
--- /dev/null
+++ b/assets/images/svg/bloodcholestrol.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/images/svg/bloodsugar.svg b/assets/images/svg/bloodsugar.svg
new file mode 100644
index 0000000..a97032c
--- /dev/null
+++ b/assets/images/svg/bloodsugar.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bulb.svg b/assets/images/svg/bulb.svg
new file mode 100644
index 0000000..94553a5
--- /dev/null
+++ b/assets/images/svg/bulb.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/due_date_accordion.svg b/assets/images/svg/due_date_accordion.svg
new file mode 100644
index 0000000..828a1c9
--- /dev/null
+++ b/assets/images/svg/due_date_accordion.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/images/svg/fertile_window_accordion.svg b/assets/images/svg/fertile_window_accordion.svg
new file mode 100644
index 0000000..63f0173
--- /dev/null
+++ b/assets/images/svg/fertile_window_accordion.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/images/svg/next_period_accordion.svg b/assets/images/svg/next_period_accordion.svg
new file mode 100644
index 0000000..4ff65db
--- /dev/null
+++ b/assets/images/svg/next_period_accordion.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/pregnancy_test_day_accordion.svg b/assets/images/svg/pregnancy_test_day_accordion.svg
new file mode 100644
index 0000000..5a29588
--- /dev/null
+++ b/assets/images/svg/pregnancy_test_day_accordion.svg
@@ -0,0 +1,20 @@
+
diff --git a/assets/images/svg/switch.svg b/assets/images/svg/switch.svg
new file mode 100644
index 0000000..1db8753
--- /dev/null
+++ b/assets/images/svg/switch.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/triglycerides.svg b/assets/images/svg/triglycerides.svg
new file mode 100644
index 0000000..0f8facd
--- /dev/null
+++ b/assets/images/svg/triglycerides.svg
@@ -0,0 +1,5 @@
+
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index 9bab873..a3cf213 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -14,8 +14,8 @@ var PACKAGES_ORDERS = '/api/orders';
var PACKAGES_ORDER_HISTORY = '/api/orders/items';
var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara';
// var BASE_URL = 'http://10.50.100.198:2018/';
-var BASE_URL = 'https://uat.hmgwebservices.com/';
-// var BASE_URL = 'https://hmgwebservices.com/';
+// var BASE_URL = 'https://uat.hmgwebservices.com/';
+var BASE_URL = 'https://hmgwebservices.com/';
// var BASE_URL = 'http://10.201.204.103/';
// var BASE_URL = 'https://orash.cloudsolutions.com.sa/';
// var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/';
@@ -700,7 +700,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb
class ApiConsts {
static const maxSmallScreen = 660;
- static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
+ static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index bbe7d75..9ea7fcc 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -247,6 +247,21 @@ class AppAssets {
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
+
+
+ static const String bloodSugar = '$svgBasePath/bloodsugar.svg';
+ static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg';
+ static const String triglycerides = '$svgBasePath/triglycerides.svg';
+ static const String bulb = '$svgBasePath/bulb.svg';
+ static const String switchBtn = '$svgBasePath/switch.svg';
+
+
+ static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg';
+ static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg';
+ static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg';
+ static const String pregnancyDayAccordion = '$svgBasePath/pregnancy_test_day_accordion.svg';
+ static const String pregnancyDueDateAccordion = '$svgBasePath/due_date_accordion.svg';
+
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 054e1fa..5d98a78 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -45,7 +45,6 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_r
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@@ -59,6 +58,8 @@ import 'package:local_auth/local_auth.dart';
import 'package:logger/web.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+
GetIt getIt = GetIt.instance;
class AppDependencies {
@@ -135,15 +136,13 @@ class AppDependencies {
() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
- getIt.registerLazySingleton(
- () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
+ getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
- getIt.registerLazySingleton(
- () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
+ getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton(
() => PayfortViewModel(
diff --git a/lib/core/enums.dart b/lib/core/enums.dart
index 3a9effb..8fd4818 100644
--- a/lib/core/enums.dart
+++ b/lib/core/enums.dart
@@ -34,9 +34,11 @@ enum FamilyFileEnum { active, inactive, blocked, deleted, pending, rejected }
enum BodyView { front, back }
+enum HealthCalConEnum { calculator, converter }
+
enum HealthCalculatorEnum { general, women }
-enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate }
+enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides }
extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
String get displayName {
@@ -59,6 +61,12 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
return isArabic ? "فترة الإباضة" : "Ovulation Period";
case HealthCalculatorsTypeEnum.deliveryDueDate:
return isArabic ? "تاريخ استحقاق التسليم" : "Delivery Due Date";
+ case HealthCalculatorsTypeEnum.bloodSugar:
+ return isArabic ? "سكر الدم" : "Blood Sugar";
+ case HealthCalculatorsTypeEnum.bloodCholesterol:
+ return isArabic ? "كوليسترول الدم" : "Blood Cholesterol";
+ case HealthCalculatorsTypeEnum.triglycerides:
+ return isArabic ? "الدهون الثلاثية في الدم" : "Triglycerides Fat Blood";
}
}
diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart
index a2b2bb9..118c54a0 100644
--- a/lib/features/lab/lab_view_model.dart
+++ b/lib/features/lab/lab_view_model.dart
@@ -1,6 +1,5 @@
import 'dart:collection';
import 'dart:core';
-import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@@ -18,7 +17,6 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:intl/intl.dart' show DateFormat;
-import 'package:logger/logger.dart';
class LabViewModel extends ChangeNotifier {
bool isLabOrdersLoading = false;
@@ -241,7 +239,6 @@ class LabViewModel extends ChangeNotifier {
createdOn: item.createdOn,
model: item))
};
- var sortedResult = SplayTreeSet.from(uniqueTests, (a, b) => a.description?[0].toUpperCase().compareTo(b.description?[0] ?? "") ?? -1);
uniqueTestsList = uniqueTests.toList();
uniqueTestsList.sort((a, b) {
return a.description!.toLowerCase().compareTo(b.description!.toLowerCase());
@@ -326,7 +323,7 @@ class LabViewModel extends ChangeNotifier {
PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true;
notifyListeners();
- mainLabResultsByHospitals.clear;
+ mainLabResultsByHospitals.clear();
final result = await labRepo.getPatientLabResultsByHospitals(laborder,
Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
@@ -369,8 +366,7 @@ class LabViewModel extends ChangeNotifier {
LoaderBottomSheet.hideLoader();
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
- var sortedResponse = sortByFlagAndValue(apiResponse.data!);
- var recentThree = sort(sortedResponse);
+ var recentThree = sort(apiResponse.data!);
mainLabResults = recentThree;
@@ -652,19 +648,6 @@ class LabViewModel extends ChangeNotifier {
}
List sort(List original) {
- DateTime? parseVerifiedDate(String? raw) {
- if (raw == null) return null;
- final regex = RegExp(r'\/Date\((\d+)\)\/');
- final match = regex.firstMatch(raw);
- if (match != null) {
- final millis = int.tryParse(match.group(1)!);
- if (millis != null) {
-
- return DateTime.fromMillisecondsSinceEpoch(millis);
- }
- }
- return null;
- }
final copy = List.from(original);
copy.sort((a, b) {
@@ -684,8 +667,8 @@ class LabViewModel extends ChangeNotifier {
final copy = List.from(original);
copy.sort((a, b) {
- final aDate =a.time;
- final bDate = a.time;
+ final aDate = a.time;
+ final bDate = b.time;
final now = DateTime.now();
if (aDate == now && bDate == now) return 0;
if (aDate == now) return 1;
diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart
index de7f067..68e83cd 100644
--- a/lib/features/medical_file/medical_file_view_model.dart
+++ b/lib/features/medical_file/medical_file_view_model.dart
@@ -36,6 +36,12 @@ class MedicalFileViewModel extends ChangeNotifier {
List patientVaccineList = [];
List patientSickLeaveList = [];
+ List patientSickLeavesByClinic = [];
+ List patientSickLeavesByHospital = [];
+ List patientSickLeavesViewList = [];
+
+ bool isSickLeavesSortByClinic = true;
+
List patientAllergiesList = [];
List patientMedicalReportList = [];
@@ -44,6 +50,12 @@ class MedicalFileViewModel extends ChangeNotifier {
List patientMedicalReportReadyList = [];
List patientMedicalReportCancelledList = [];
+ List patientMedicalReportsByClinic = [];
+ List patientMedicalReportsByHospital = [];
+ List patientMedicalReportsViewList = [];
+
+ bool isMedicalReportsSortByClinic = true;
+
List patientMedicalReportAppointmentHistoryList = [];
PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment;
@@ -90,6 +102,49 @@ class MedicalFileViewModel extends ChangeNotifier {
} else if (index == 2) {
patientMedicalReportList = patientMedicalReportCancelledList;
}
+ // Re-group by clinic/hospital for the selected tab
+ _groupMedicalReportsByClinicAndHospital();
+ notifyListeners();
+ }
+
+ void _groupMedicalReportsByClinicAndHospital() {
+ patientMedicalReportsByClinic.clear();
+ patientMedicalReportsByHospital.clear();
+
+ for (var element in patientMedicalReportList) {
+ // Group by clinic
+ List reportsByClinic = patientMedicalReportsByClinic.where((elementClinic) => elementClinic.filterName == element.clinicDescription).toList();
+
+ if (reportsByClinic.isNotEmpty) {
+ patientMedicalReportsByClinic[patientMedicalReportsByClinic.indexOf(reportsByClinic[0])].medicalReportsList!.add(element);
+ } else {
+ patientMedicalReportsByClinic.add(MedicalReportList(filterName: element.clinicDescription, medicalReport: element));
+ }
+
+ // Group by hospital
+ List reportsByHospital = patientMedicalReportsByHospital.where((elementHospital) => elementHospital.filterName == element.projectName).toList();
+
+ if (reportsByHospital.isNotEmpty) {
+ patientMedicalReportsByHospital[patientMedicalReportsByHospital.indexOf(reportsByHospital[0])].medicalReportsList!.add(element);
+ } else {
+ patientMedicalReportsByHospital.add(MedicalReportList(filterName: element.projectName, medicalReport: element));
+ }
+ }
+
+ if (isMedicalReportsSortByClinic) {
+ patientMedicalReportsViewList = patientMedicalReportsByClinic;
+ } else {
+ patientMedicalReportsViewList = patientMedicalReportsByHospital;
+ }
+ }
+
+ setIsMedicalReportsSortByClinic(bool value) {
+ isMedicalReportsSortByClinic = value;
+ if (isMedicalReportsSortByClinic) {
+ patientMedicalReportsViewList = patientMedicalReportsByClinic;
+ } else {
+ patientMedicalReportsViewList = patientMedicalReportsByHospital;
+ }
notifyListeners();
}
@@ -110,17 +165,35 @@ class MedicalFileViewModel extends ChangeNotifier {
setIsPatientSickLeaveListLoading(bool val) {
if (val) {
patientSickLeaveList.clear();
+ patientSickLeavesByClinic.clear();
+ patientSickLeavesByHospital.clear();
+ patientSickLeavesViewList.clear();
patientSickLeavePDFBase64 = "";
+ isSickLeavesSortByClinic = true;
}
isPatientSickLeaveListLoading = val;
notifyListeners();
}
+ setIsSickLeavesSortByClinic(bool value) {
+ isSickLeavesSortByClinic = value;
+ if (isSickLeavesSortByClinic) {
+ patientSickLeavesViewList = patientSickLeavesByClinic;
+ } else {
+ patientSickLeavesViewList = patientSickLeavesByHospital;
+ }
+ notifyListeners();
+ }
+
setIsPatientMedicalReportsLoading(bool val) {
if (val) {
onMedicalReportTabChange(0);
patientMedicalReportList.clear();
+ patientMedicalReportsByClinic.clear();
+ patientMedicalReportsByHospital.clear();
+ patientMedicalReportsViewList.clear();
patientMedicalReportPDFBase64 = "";
+ isMedicalReportsSortByClinic = true;
}
isPatientMedicalReportsListLoading = val;
notifyListeners();
@@ -214,6 +287,29 @@ class MedicalFileViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
patientSickLeaveList = apiResponse.data!;
isPatientSickLeaveListLoading = false;
+
+ // Group by clinic and hospital
+ for (var element in patientSickLeaveList) {
+ // Group by clinic
+ List sickLeavesByClinic = patientSickLeavesByClinic.where((elementClinic) => elementClinic.filterName == element.clinicName).toList();
+
+ if (sickLeavesByClinic.isNotEmpty) {
+ patientSickLeavesByClinic[patientSickLeavesByClinic.indexOf(sickLeavesByClinic[0])].sickLeavesList!.add(element);
+ } else {
+ patientSickLeavesByClinic.add(SickLeaveList(filterName: element.clinicName, sickLeaves: element));
+ }
+
+ // Group by hospital
+ List sickLeavesByHospital = patientSickLeavesByHospital.where((elementHospital) => elementHospital.filterName == element.projectName).toList();
+
+ if (sickLeavesByHospital.isNotEmpty) {
+ patientSickLeavesByHospital[patientSickLeavesByHospital.indexOf(sickLeavesByHospital[0])].sickLeavesList!.add(element);
+ } else {
+ patientSickLeavesByHospital.add(SickLeaveList(filterName: element.projectName, sickLeaves: element));
+ }
+ }
+ patientSickLeavesViewList = patientSickLeavesByClinic;
+
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
diff --git a/lib/features/medical_file/models/patient_medical_response_model.dart b/lib/features/medical_file/models/patient_medical_response_model.dart
index 52785af..3bcf5cd 100644
--- a/lib/features/medical_file/models/patient_medical_response_model.dart
+++ b/lib/features/medical_file/models/patient_medical_response_model.dart
@@ -190,3 +190,16 @@ class PatientMedicalReportResponseModel {
return data;
}
}
+
+class MedicalReportList {
+ String? filterName;
+ List? medicalReportsList;
+
+ MedicalReportList({this.filterName, PatientMedicalReportResponseModel? medicalReport}) {
+ medicalReportsList = [];
+ if (medicalReport != null) {
+ medicalReportsList!.add(medicalReport);
+ }
+ }
+}
+
diff --git a/lib/features/medical_file/models/patient_sickleave_response_model.dart b/lib/features/medical_file/models/patient_sickleave_response_model.dart
index 3bf732c..53c886f 100644
--- a/lib/features/medical_file/models/patient_sickleave_response_model.dart
+++ b/lib/features/medical_file/models/patient_sickleave_response_model.dart
@@ -174,3 +174,16 @@ class PatientSickLeavesResponseModel {
return data;
}
}
+
+class SickLeaveList {
+ String? filterName;
+ List? sickLeavesList;
+
+ SickLeaveList({this.filterName, PatientSickLeavesResponseModel? sickLeaves}) {
+ sickLeavesList = [];
+ if (sickLeaves != null) {
+ sickLeavesList!.add(sickLeaves);
+ }
+ }
+}
+
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index d077970..b4a25ee 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -60,6 +60,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List patientEyeMeasurementsAppointmentsHistoryList = [];
+ // Grouping by Clinic and Hospital
+ List patientAppointmentsByClinic = [];
+ List patientAppointmentsByHospital = [];
+ List patientAppointmentsViewList = [];
+ bool isAppointmentsSortByClinic = true;
+
List askDoctorRequestTypeList = [];
PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel;
@@ -82,6 +88,43 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
+ setIsAppointmentsSortByClinic(bool value) {
+ isAppointmentsSortByClinic = value;
+ _groupAppointmentsByClinicAndHospital();
+ notifyListeners();
+ }
+
+ void _groupAppointmentsByClinicAndHospital() {
+ patientAppointmentsByClinic.clear();
+ patientAppointmentsByHospital.clear();
+
+ for (var element in filteredAppointmentList) {
+ // Group by clinic
+ List appointmentsByClinic = patientAppointmentsByClinic.where((elementClinic) => elementClinic.filterName == element.clinicName).toList();
+
+ if (appointmentsByClinic.isNotEmpty) {
+ patientAppointmentsByClinic[patientAppointmentsByClinic.indexOf(appointmentsByClinic[0])].patientDoctorAppointmentList!.add(element);
+ } else {
+ patientAppointmentsByClinic.add(PatientAppointmentList(filterName: element.clinicName, patientDoctorAppointment: element));
+ }
+
+ // Group by hospital
+ List appointmentsByHospital = patientAppointmentsByHospital.where((elementHospital) => elementHospital.filterName == element.projectName).toList();
+
+ if (appointmentsByHospital.isNotEmpty) {
+ patientAppointmentsByHospital[patientAppointmentsByHospital.indexOf(appointmentsByHospital[0])].patientDoctorAppointmentList!.add(element);
+ } else {
+ patientAppointmentsByHospital.add(PatientAppointmentList(filterName: element.projectName, patientDoctorAppointment: element));
+ }
+ }
+
+ if (isAppointmentsSortByClinic) {
+ patientAppointmentsViewList = patientAppointmentsByClinic;
+ } else {
+ patientAppointmentsViewList = patientAppointmentsByHospital;
+ }
+ }
+
initAppointmentsViewModel() {
if (isAppointmentDataToBeLoaded) {
patientAppointmentsHistoryList.clear();
@@ -513,6 +556,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
break;
}
getFiltersForSelectedAppointmentList(filteredAppointmentList);
+ _groupAppointmentsByClinicAndHospital();
notifyListeners();
}
@@ -570,6 +614,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} catch (e) {}
}
}
+ _groupAppointmentsByClinicAndHospital();
notifyListeners();
}
diff --git a/lib/main.dart b/lib/main.dart
index ed69150..fd7654f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -32,7 +32,7 @@ import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart
index 934aca4..abf2464 100644
--- a/lib/presentation/appointments/my_appointments_page.dart
+++ b/lib/presentation/appointments/my_appointments_page.dart
@@ -4,6 +4,9 @@ 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_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/utils/date_util.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';
@@ -35,6 +38,7 @@ class MyAppointmentsPage extends StatefulWidget {
}
class _MyAppointmentsPageState extends State {
+ int? expandedIndex;
late MyAppointmentsViewModel myAppointmentsViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@@ -68,6 +72,9 @@ class _MyAppointmentsPageState extends State {
CustomTabBarModel(null, "Completed".needTranslation),
],
onTabChange: (index) {
+ setState(() {
+ expandedIndex = null;
+ });
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read().flush();
@@ -91,17 +98,55 @@ class _MyAppointmentsPageState extends State {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ SizedBox(height: 16.h),
+ // Clinic & Hospital Sort
+ Row(
+ children: [
+ CustomButton(
+ text: LocaleKeys.byClinic.tr(context: context),
+ onPressed: () {
+ myAppointmentsVM.setIsAppointmentsSortByClinic(true);
+ },
+ backgroundColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
+ borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
+ textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: LocaleKeys.byHospital.tr(context: context),
+ onPressed: () {
+ myAppointmentsVM.setIsAppointmentsSortByClinic(false);
+ },
+ backgroundColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
+ borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
+ textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
Visibility(visible: myAppointmentsVM.availableFilters.isNotEmpty, child: getAppointmentFilters(myAppointmentsVM)),
- ListView.separated(
- padding: EdgeInsets.only(top: 16.h),
+ SizedBox(height: 8.h),
+ // Expandable list
+ ListView.builder(
+ padding: EdgeInsets.only(top: 8.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: myAppointmentsVM.isMyAppointmentsLoading
- ? 5
+ ? 4
: filteredAppointmentList.isNotEmpty
- ? filteredAppointmentList.length
+ ? myAppointmentsVM.patientAppointmentsViewList.length
: 1,
itemBuilder: (context, index) {
+ final isExpanded = expandedIndex == index;
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
@@ -123,15 +168,149 @@ class _MyAppointmentsPageState extends State {
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
- child: AppointmentCard(
- patientAppointmentHistoryResponseModel: filteredAppointmentList[index],
- myAppointmentsViewModel: myAppointmentsViewModel,
- bookAppointmentsViewModel: bookAppointmentsViewModel,
- isLoading: false,
- isFromHomePage: false,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CustomButton(
+ text: "${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} Appointments",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 30.h,
+ ),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ myAppointmentsVM.patientAppointmentsViewList[index].filterName!.toText16(isBold: true)
+ ],
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.map((appointment) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Image.network(
+ appointment.doctorImageURL!,
+ width: 24.h,
+ height: 24.h,
+ fit: BoxFit.fill,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(child: appointment.doctorNameObj!.toText14(weight: FontWeight.w500)),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ CustomButton(
+ text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointment.appointmentDate), false),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: myAppointmentsVM.isAppointmentsSortByClinic ? appointment.projectName! : appointment.clinicName!,
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: appointment.statusDesc ?? "",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ AppointmentCard(
+ patientAppointmentHistoryResponseModel: appointment,
+ myAppointmentsViewModel: myAppointmentsViewModel,
+ bookAppointmentsViewModel: bookAppointmentsViewModel,
+ isLoading: false,
+ isFromHomePage: false,
+ ),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
+ ],
+ );
+ }),
+ ],
+ ),
+ )
+ : SizedBox.shrink(),
+ ),
+ ],
+ ),
),
- ).paddingSymmetrical(24.h, 0.h),
+ ),
),
),
)
@@ -160,8 +339,7 @@ class _MyAppointmentsPageState extends State {
).paddingSymmetrical(48.h, 0.h),
);
},
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
- ),
+ ).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
],
);
diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart
index 51e9703..3740a4a 100644
--- a/lib/presentation/appointments/my_doctors_page.dart
+++ b/lib/presentation/appointments/my_doctors_page.dart
@@ -1,6 +1,5 @@
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_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
@@ -21,169 +20,377 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../../widgets/chip/app_custom_chip_widget.dart';
+import '../../widgets/buttons/custom_button.dart';
-class MyDoctorsPage extends StatelessWidget {
+class MyDoctorsPage extends StatefulWidget {
MyDoctorsPage({super.key});
+ @override
+ State createState() => _MyDoctorsPageState();
+}
+
+class _MyDoctorsPageState extends State {
late MyAppointmentsViewModel myAppointmentsViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
+ bool isSortByClinic = true; // default like lab page
+ int? expandedIndex;
+ final Map _groupKeys = {};
+
@override
Widget build(BuildContext context) {
- AppState appState = getIt.get();
myAppointmentsViewModel = Provider.of(context, listen: false);
bookAppointmentsViewModel = Provider.of(context, listen: false);
- return Scaffold(
- backgroundColor: AppColors.bgScaffoldColor,
- body: CollapsingListView(
+
+ return CollapsingListView(
title: LocaleKeys.myDoctor.tr(context: context),
- child: SingleChildScrollView(
- child: Consumer(builder: (context, myAppointmentsVM, child) {
+ child: Consumer(builder: (context, myAppointmentsVM, child) {
+ // build grouped lists from the flat list
+ final clinicMap = >{};
+ final hospitalMap = >{};
+
+ for (var doc in myAppointmentsVM.patientMyDoctorsList) {
+ final clinicKey = (doc.clinicName ?? 'Unknown').trim();
+ clinicMap.putIfAbsent(clinicKey, () => []).add(doc);
+
+ final hospitalKey = (doc.projectName ?? doc.projectID?.toString() ?? 'Unknown').toString().trim();
+ hospitalMap.putIfAbsent(hospitalKey, () => []).add(doc);
+ }
+
+ final patientMyDoctorsByClinic = clinicMap.values.toList();
+ final patientMyDoctorsByHospital = hospitalMap.values.toList();
+ final patientMyDoctorsViewList = isSortByClinic ? patientMyDoctorsByClinic : patientMyDoctorsByHospital;
+
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
+ // Add Clinic / Hospital toggle buttons styled like LabOrdersPage
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 24.h),
+ child: Row(
+ children: [
+ CustomButton(
+ text: LocaleKeys.byClinic.tr(context: context),
+ onPressed: () {
+ setState(() {
+ isSortByClinic = true;
+ expandedIndex = null;
+ });
+ },
+ backgroundColor: isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
+ borderColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
+ textColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+
+
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: LocaleKeys.byHospital.tr(context: context),
+ onPressed: () {
+ setState(() {
+ isSortByClinic = false;
+ expandedIndex = null;
+ });
+ },
+ backgroundColor: isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
+ borderColor: isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
+ textColor: isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+
+ ),
+ ],
+ ),
+ ),
+ SizedBox(height: 16.h),
ListView.separated(
scrollDirection: Axis.vertical,
- itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length,
+ itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : (patientMyDoctorsViewList.isNotEmpty ? patientMyDoctorsViewList.length : 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(left: 24.h, right: 24.h),
itemBuilder: (context, index) {
- return myAppointmentsVM.isPatientMyDoctorsLoading
- ? Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
- child: Column(
+ if (myAppointmentsVM.isPatientMyDoctorsLoading) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Image.network(
- "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
- width: 63.h,
- height: 63.h,
- fit: BoxFit.cover,
- ).circle(100).toShimmer2(isShow: true),
- SizedBox(width: 16.h),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true),
- SizedBox(height: 8.h),
- Wrap(
- direction: Axis.horizontal,
- spacing: 3.h,
- runSpacing: 4.h,
- children: [
- AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
- AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
- ],
- ),
- ],
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- )
- : AnimationConfiguration.staggeredList(
- position: index,
- duration: const Duration(milliseconds: 1000),
- child: SlideAnimation(
- verticalOffset: 100.0,
- child: FadeInAnimation(
- child: Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
+ Image.network(
+ "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
+ width: 63.h,
+ height: 63.h,
+ fit: BoxFit.cover,
+ ).circle(100).toShimmer2(isShow: true),
+ SizedBox(width: 16.h),
+ Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Image.network(
- myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
- width: 63.h,
- height: 63.h,
- fit: BoxFit.cover,
- ).circle(100).toShimmer2(isShow: false),
- SizedBox(width: 16.h),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- (myAppointmentsVM.patientMyDoctorsList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false),
- SizedBox(height: 8.h),
- Wrap(
- direction: Axis.horizontal,
- spacing: 3.h,
- runSpacing: 4.h,
- children: [
- AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].clinicName).toShimmer2(isShow: false, width: 16.h),
- AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].projectName).toShimmer2(isShow: false, width: 16.h),
- ],
- ),
- ],
- ),
- ),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true),
+ SizedBox(height: 8.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 3.h,
+ runSpacing: 4.h,
children: [
- "".toText16(),
- Transform.flip(
- flipX: appState.isArabic(),
- child:
- Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
+ AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
+ AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
],
),
],
),
),
- ).onPress(() async {
- bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
- clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID,
- projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID,
- doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID,
- ));
- LoaderBottomSheet.showLoader();
- await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
- LoaderBottomSheet.hideLoader();
- Navigator.of(context).push(
- CustomPageRoute(
- page: DoctorProfilePage(),
- ),
- );
- }, onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- }),
+ ],
),
+ ],
+ ),
+ ),
+ );
+ }
+
+ final group = patientMyDoctorsViewList[index];
+ final displayName = isSortByClinic ? (group.first.clinicName ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
+ final isExpanded = expandedIndex == index;
+
+ return Container(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
+ margin: EdgeInsets.only(bottom: 12.h),
+ padding: EdgeInsets.all(16.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ GestureDetector(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys[index];
+ if (key != null && key.currentContext != null && expandedIndex == index) {
+ Scrollable.ensureVisible(
+ key.currentContext!,
+ duration: Duration(milliseconds: 350),
+ curve: Curves.easeInOut,
+ alignment: 0.1,
+ );
+ }
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CustomButton(
+ text: "${group.length} ${'doctors'.needTranslation}",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 30.h,
+ ),
+ Icon(isExpanded ? Icons.expand_less : Icons.chevron_right, color: AppColors.greyColor),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Text(
+ displayName,
+ style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
),
- );
+ ),
+ AnimatedSwitcher(
+ duration: Duration(milliseconds: 400),
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.only(top: 12.h),
+ child: Column(
+ children: group.map((doctor) {
+ return Container(
+ margin: EdgeInsets.only(bottom: 12.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 12.h,
+ hasShadow: true,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Image.network(
+ (doctor?.doctorImageURL ?? doctor?.doctorImage ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png"),
+ width: 24.h,
+ height: 24.h,
+ fit: BoxFit.cover,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
+ SizedBox(height: 6.h),
+ ],
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ CustomButton(
+ text: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Expanded(
+ flex: 6,
+ child: CustomButton(
+ text: "View Profile".needTranslation.tr(context: context),
+ onPressed: () async {
+ bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
+ clinicID: doctor?.clinicID ?? 0,
+ projectID: doctor?.projectID ?? 0,
+ doctorID: doctor?.doctorID ?? 0,
+ ));
+ LoaderBottomSheet.showLoader();
+ await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
+ LoaderBottomSheet.hideLoader();
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: DoctorProfilePage(),
+ ),
+ );
+ }, onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ },
+ backgroundColor: AppColors.bgRedLightColor,
+ borderColor: AppColors.primaryRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ),
+ SizedBox(width: 8.h),
+ Expanded(
+ flex: 1,
+ child: Container(
+ height: 40.h,
+ width: 40.w,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.textColor,
+ borderRadius: 12,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(12.h),
+ child: Transform.flip(
+ flipX: getIt().isArabic(),
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.forward_arrow_icon_small,
+ iconColor: AppColors.whiteColor,
+ fit: BoxFit.contain,
+ ),
+ ),
+ ),
+ ).onPress(() async {
+ bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
+ clinicID: doctor?.clinicID ?? 0,
+ projectID: doctor?.projectID ?? 0,
+ doctorID: doctor?.doctorID ?? 0,
+ ));
+ LoaderBottomSheet.showLoader();
+ await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
+ LoaderBottomSheet.hideLoader();
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: DoctorProfilePage(),
+ ),
+ );
+ }, onError: (err) {
+ LoaderBottomSheet.hideLoader();
+ showCommonBottomSheetWithoutHeight(
+ context,
+ child: Utils.getErrorWidget(loadingText: err),
+ callBackFunc: () {},
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ );
+ });
+ }),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }).toList(),
+ ),
+ )
+ : SizedBox.shrink(),
+ ),
+ ],
+ ),
+ );
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
@@ -191,8 +398,6 @@ class MyDoctorsPage extends StatelessWidget {
],
);
}),
- ),
- ),
);
}
}
diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart
index a35c1ca..50659ee 100644
--- a/lib/presentation/appointments/widgets/appointment_card.dart
+++ b/lib/presentation/appointments/widgets/appointment_card.dart
@@ -353,14 +353,14 @@ class AppointmentCard extends StatelessWidget {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
- fontSize: 14.f,
+ fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppAssets.rebook_appointment_icon,
iconColor: AppColors.blackColor,
- iconSize: 16.h,
+ iconSize: 14.h,
);
}
diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
index 6b5dde7..ccf6674 100644
--- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart
+++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart
@@ -167,14 +167,14 @@ class AppointmentDoctorCard extends StatelessWidget {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
- fontSize: 14.f,
+ fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: 40.h,
icon: AppAssets.rebook_appointment_icon,
iconColor: AppColors.blackColor,
- iconSize: 16.h,
+ iconSize: 14.h,
);
} else {
return patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false
diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart
index df2f85a..d801bdb 100644
--- a/lib/presentation/book_appointment/select_clinic_page.dart
+++ b/lib/presentation/book_appointment/select_clinic_page.dart
@@ -6,6 +6,7 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/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';
@@ -27,6 +28,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
@@ -36,8 +38,10 @@ import 'package:provider/provider.dart';
class SelectClinicPage extends StatefulWidget {
bool isFromRegionFlow;
+ HealthCalculatorsTypeEnum? calculatorType;
+ dynamic calculatedResult;
- SelectClinicPage({super.key, this.isFromRegionFlow = false});
+ SelectClinicPage({super.key, this.isFromRegionFlow = false, this.calculatorType, this.calculatedResult});
@override
State createState() => _SelectClinicPageState();
@@ -69,131 +73,872 @@ class _SelectClinicPageState extends State {
bookAppointmentsViewModel = Provider.of(context, listen: false);
regionalViewModel = Provider.of(context, listen: true);
appState = getIt.get();
- return Scaffold(
- backgroundColor: AppColors.bgScaffoldColor,
- body: CollapsingListView(
- title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context),
- child: SingleChildScrollView(
- child: Padding(
- padding: EdgeInsets.symmetric(horizontal: 24.h),
- child: Consumer(builder: (context, bookAppointmentsVM, child) {
- return Column(
- children: [
- SizedBox(height: 16.h),
- TextInputWidget(
- labelText: LocaleKeys.search.tr(context: context),
- hintText: LocaleKeys.clinicName.tr(context: context),
- controller: searchEditingController,
- isEnable: true,
- prefix: null,
- autoFocus: false,
- isBorderAllowed: false,
- keyboardType: TextInputType.text,
- focusNode: textFocusNode,
- suffix: searchEditingController.text.isNotEmpty
- ? GestureDetector(
- onTap: () {
- searchEditingController.clear();
- bookAppointmentsViewModel.filterClinics("");
- textFocusNode.unfocus();
- },
- child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
- )
- : null,
- onChange: (value) {
- bookAppointmentsViewModel.filterClinics(value!);
- },
- padding: EdgeInsets.symmetric(
- vertical: ResponsiveExtension(10).h,
- horizontal: ResponsiveExtension(15).h,
+ if (widget.calculatorType != null) {
+ return CollapsingListView(
+ title: "Your ${widget.calculatorType!.name.toCamelCase}",
+ bottomChild: Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
+ padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
+ child: CustomButton(
+ text: LocaleKeys.bookAppo.tr(),
+ onPressed: () {
+ onClinicSelected(GetClinicsListResponseModel(clinicID: 108, clinicDescription: "asdfds", clinicDescriptionN: "asdfds"));
+ },
+ icon: null,
+ fontSize: 16.f,
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedColor,
+ borderRadius: 12.r,
+ fontWeight: FontWeight.w500),
+ ),
+ child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h),
+ );
+ } else {
+ return Scaffold(
+ backgroundColor: AppColors.bgScaffoldColor,
+ body: CollapsingListView(
+ title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context),
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 24.h),
+ child: Consumer(builder: (context, bookAppointmentsVM, child) {
+ return Column(
+ children: [
+ SizedBox(height: 16.h),
+ TextInputWidget(
+ labelText: LocaleKeys.search.tr(context: context),
+ hintText: LocaleKeys.clinicName.tr(context: context),
+ controller: searchEditingController,
+ isEnable: true,
+ prefix: null,
+ autoFocus: false,
+ isBorderAllowed: false,
+ keyboardType: TextInputType.text,
+ focusNode: textFocusNode,
+ suffix: searchEditingController.text.isNotEmpty
+ ? GestureDetector(
+ onTap: () {
+ searchEditingController.clear();
+ bookAppointmentsViewModel.filterClinics("");
+ textFocusNode.unfocus();
+ },
+ child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
+ )
+ : null,
+ onChange: (value) {
+ bookAppointmentsViewModel.filterClinics(value!);
+ },
+ padding: EdgeInsets.symmetric(
+ vertical: ResponsiveExtension(10).h,
+ horizontal: ResponsiveExtension(15).h,
+ ),
),
- ),
- bookAppointmentsVM.isLiveCareSchedule
- ? ListView.separated(
- padding: EdgeInsets.only(top: 24.h),
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length,
- itemBuilder: (context, index) {
- return bookAppointmentsVM.isClinicsListLoading
- ? ClinicCard(
- bookAppointmentsVM: bookAppointmentsVM,
- liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
- clinicsListResponseModel: GetClinicsListResponseModel(),
- isLoading: bookAppointmentsVM.isClinicsListLoading,
- )
- : AnimationConfiguration.staggeredList(
- position: index,
- duration: const Duration(milliseconds: 500),
- child: SlideAnimation(
- verticalOffset: 100.0,
- child: FadeInAnimation(
- child: AnimatedContainer(
- duration: Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
- child: ClinicCard(
- bookAppointmentsVM: bookAppointmentsVM,
- liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index],
- clinicsListResponseModel: GetClinicsListResponseModel(),
- isLoading: bookAppointmentsVM.isClinicsListLoading,
- ).onPress(() {
- onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]);
- }),
+ bookAppointmentsVM.isLiveCareSchedule
+ ? ListView.separated(
+ padding: EdgeInsets.only(top: 24.h),
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length,
+ itemBuilder: (context, index) {
+ return bookAppointmentsVM.isClinicsListLoading
+ ? ClinicCard(
+ bookAppointmentsVM: bookAppointmentsVM,
+ liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
+ clinicsListResponseModel: GetClinicsListResponseModel(),
+ isLoading: bookAppointmentsVM.isClinicsListLoading,
+ )
+ : AnimationConfiguration.staggeredList(
+ position: index,
+ duration: const Duration(milliseconds: 500),
+ child: SlideAnimation(
+ verticalOffset: 100.0,
+ child: FadeInAnimation(
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
+ child: ClinicCard(
+ bookAppointmentsVM: bookAppointmentsVM,
+ liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index],
+ clinicsListResponseModel: GetClinicsListResponseModel(),
+ isLoading: bookAppointmentsVM.isClinicsListLoading,
+ ).onPress(() {
+ onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]);
+ }),
+ ),
),
),
- ),
- );
- },
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
- )
- : ListView.separated(
- padding: EdgeInsets.only(top: 24.h),
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length,
- itemBuilder: (context, index) {
- return bookAppointmentsVM.isClinicsListLoading
- ? ClinicCard(
- bookAppointmentsVM: bookAppointmentsVM,
- liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
- clinicsListResponseModel: GetClinicsListResponseModel(),
- isLoading: bookAppointmentsVM.isClinicsListLoading,
- )
- : AnimationConfiguration.staggeredList(
- position: index,
- duration: const Duration(milliseconds: 500),
- child: SlideAnimation(
- verticalOffset: 100.0,
- child: FadeInAnimation(
- child: AnimatedContainer(
- duration: Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
- child: ClinicCard(
- bookAppointmentsVM: bookAppointmentsVM,
- liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
- clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index],
- isLoading: bookAppointmentsVM.isClinicsListLoading,
- ).onPress(() {
- onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]);
- }),
+ );
+ },
+ separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
+ )
+ : ListView.separated(
+ padding: EdgeInsets.only(top: 24.h),
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length,
+ itemBuilder: (context, index) {
+ return bookAppointmentsVM.isClinicsListLoading
+ ? ClinicCard(
+ bookAppointmentsVM: bookAppointmentsVM,
+ liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
+ clinicsListResponseModel: GetClinicsListResponseModel(),
+ isLoading: bookAppointmentsVM.isClinicsListLoading,
+ )
+ : AnimationConfiguration.staggeredList(
+ position: index,
+ duration: const Duration(milliseconds: 500),
+ child: SlideAnimation(
+ verticalOffset: 100.0,
+ child: FadeInAnimation(
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
+ child: ClinicCard(
+ bookAppointmentsVM: bookAppointmentsVM,
+ liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(),
+ clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index],
+ isLoading: bookAppointmentsVM.isClinicsListLoading,
+ ).onPress(() {
+ onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]);
+ }),
+ ),
),
),
- ),
- );
- },
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
+ );
+ },
+ separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
+ ),
+ ],
+ );
+ }),
+ ),
+ ),
+ ),
+ );
+ }
+ }
+
+ Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
+ switch (widget.calculatorType!) {
+ case HealthCalculatorsTypeEnum.bmi:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.calories:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bmr:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.idealBodyWeight:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bodyFat:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.crabsProteinFat:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.ovulation:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.deliveryDueDate:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bloodSugar:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bloodCholesterol:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.triglycerides:
+ return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult);
+ }
+ }
+
+ Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
+ print(calculatedResult);
+ return Column(
+ children: [
+ if (type == HealthCalculatorsTypeEnum.bmi) ...[
+ if (calculatedResult["bmiCategory"] == "Normal") ...[
+ calorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.',
+ color: Colors.green,
+ icon: Icons.scale,
+ hide: true,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[
+ calorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.',
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[
+ calorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.',
+ color: Colors.red,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.calories) ...[
+ if (calculatedResult["calorieRange"] == "Normal") ...[
+ calorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.',
+ color: Colors.green,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["calorieRange"] == "Low") ...[
+ calorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.',
+ color: Colors.orange,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["calorieRange"] == "High") ...[
+ calorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.',
+ color: Colors.red,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[
+ if (calculatedResult["bmrRange"] == "Normal") ...[
+ calorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.',
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmrRange"] == "Low") ...[
+ calorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.',
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmrRange"] == "High") ...[
+ calorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.',
+ color: Colors.red,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[
+ if (calculatedResult["status"] == "Normal") ...[
+ calorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ if (diff == 0) {
+ return 'Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.';
+ } else if (diff > 0) {
+ return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.';
+ } else {
+ return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.';
+ }
+ }
+ return 'Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.';
+ })(),
+ color: Colors.green,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["status"] == "Underweight") ...[
+ calorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.';
+ }
+ return 'You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.';
+ })(),
+ color: Colors.orange,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["status"] == "Overweight") ...[
+ calorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.';
+ }
+ return 'You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.';
+ })(),
+ color: Colors.red,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[
+ if (calculatedResult["dietType"] == "Very Low Crabs") ...[
+ calorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.';
+ // if (total is num) {
+ // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ // }
+ return base;
+ })(),
+ chipsItems: [
+ ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'),
+ ],
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Low Crabs") ...[
+ calorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.';
+ // if (total is num) {
+ // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ // }
+ return base;
+ })(),
+ chipsItems: [
+ ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'),
+ ],
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[
+ calorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.';
+ // if (total is num) {
+ // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ // }
+ return base;
+ })(),
+ chipsItems: [
+ ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'),
+ ],
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[
+ calorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.';
+ // if (total is num) {
+ // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ // }
+
+ return base;
+ })(),
+ chipsItems: [
+ ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'),
+ ],
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Zone Diet") ...[
+ calorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.';
+ // if (total is num) {
+ // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ // }
+ return base;
+ })(),
+ chipsItems: [
+ ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'),
+ ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'),
+ ],
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.ovulation) ...[
+ Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.nextPeriodAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
),
- ],
- );
- }),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: calculatedResult["lastPeriodDay"].toString(),
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ SizedBox(height: 16.h),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: calculatedResult["ovulationDay"].toString(),
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ SizedBox(height: 16.h),
+ "Fertility Window".toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ SizedBox(
+ width: 4.w,
+ ),
+ "-".toText16(isBold: true, color: AppColors.successColor),
+ SizedBox(
+ width: 4.w,
+ ),
+ calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range."
+ .toText12(height: 1.5, color: AppColors.textColorLight, fontWeight: FontWeight.w500)
+ ],
+ ),
+ ),
+ SizedBox(
+ height: 16.h,
+ ),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.bulb),
+ SizedBox(
+ width: 8.w,
+ ),
+ Row(
+ children: [
+ "Tips to keep in mind".toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ ],
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Column(
+ children: [
+ _buildTipRow("Drink before you feel thirsty."),
+ SizedBox(height: 4.h),
+ _buildTipRow("Keep a refillable bottle next to you."),
+ SizedBox(height: 4.h),
+ _buildTipRow("Track your daily intake to stay motivated."),
+ SizedBox(height: 4.h),
+ _buildTipRow("Choose sparkling water instead of soda."),
+ ],
+ ).paddingOnly(left: 4.w, right: 4.w)
+ ],
+ ),
+ ),
+ ],
+ )
+ ] else if (type == HealthCalculatorsTypeEnum.deliveryDueDate) ...[
+ Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDueDateAccordion, width: 30.h, height: 30.h),
+ SizedBox(width: 8.w),
+ calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ Expanded(child: SizedBox(width: 8.w)),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: calculatedResult["dueDateDay"].toString(),
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ "First Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ Expanded(child: SizedBox(width: 8.h)),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: "${calculatedResult["firstTrimester"]["weeks"]} Weeks",
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ SizedBox(
+ width: 4.w,
+ ),
+ "-".toText16(isBold: true, color: AppColors.successColor),
+ SizedBox(
+ width: 4.w,
+ ),
+ calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ ],
+ ),
+ ),
+
+
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ "Second Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ Expanded(child: SizedBox(width: 8.h)),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: "${calculatedResult["secondTrimester"]["weeks"]} Weeks",
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ SizedBox(
+ width: 4.w,
+ ),
+ "-".toText16(isBold: true, color: AppColors.successColor),
+ SizedBox(
+ width: 4.w,
+ ),
+ calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ ],
+ ),
+ ),
+
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ "Third Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ Expanded(child: SizedBox(width: 8.h)),
+ CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: "${calculatedResult["thirdTrimester"]["weeks"]} Weeks",
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDayAccordion, width: 30.h, height: 30.h),
+ SizedBox(
+ width: 8.w,
+ ),
+ calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ SizedBox(
+ width: 4.w,
+ ),
+ "-".toText16(isBold: true, color: AppColors.successColor),
+ SizedBox(
+ width: 4.w,
+ ),
+ calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ ],
+ ),
+ ),
+
+
+ ],
+ )
+ ],
+ ],
+ );
+ }
+
+ Widget _buildTipRow(String text) {
+ return Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: EdgeInsets.only(top: 6.h, right: 8.w),
+ child: CircleAvatar(
+ radius: 3.r,
+ backgroundColor: AppColors.textColorLight,
),
),
+ Expanded(
+ child: text.toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w500, height: 1.2.h),
+ ),
+ ],
+ );
+ }
+
+ Widget calorieWidget({required String title, required dynamic calories, required String description, required Color color, required IconData icon, bool hide = false, List? chipsItems}) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(title), isBold: true),
+ SizedBox(width: 10.w),
+ hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600)
+ ],
+ ),
+ _buildStatusIcon(title)
+ ],
+ ),
+ SizedBox(height: 16.h),
+ if (chipsItems != null && chipsItems.isNotEmpty) ...[
+ Wrap(
+ spacing: 8.0, // Horizontal space between chips
+ runSpacing: 4.0, // Vertical space between rows of chips
+ children: chipsItems.map((item) {
+ return CustomChipWidget(
+ height: 30.h,
+ chipType: ChipTypeEnum.alert,
+ backgroundColor: AppColors.lightGrayBGColor,
+ chipText: item,
+ iconAsset: null,
+ isShowBorder: false,
+ borderRadius: 8.h,
+ textColor: AppColors.textColor)
+ .paddingOnly(bottom: 4.h);
+ }).toList(),
+ ),
+ SizedBox(height: 8.h),
+ ],
+ title.toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ SizedBox(height: 8.h),
+ description.toText12(height: 1.2.h, color: AppColors.textColorLight, fontWeight: FontWeight.w500)
+ ],
),
);
}
+ Widget _buildStatusIcon(String title) {
+ final key = title.trim();
+ if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
+ return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
+ } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
+ return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
+ } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
+ return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
+ } else {
+ return SizedBox();
+ }
+ }
+
+ Color getTextColor(String title) {
+ final key = title.trim();
+ if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
+ return AppColors.successColor;
+ } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
+ return AppColors.warningColor;
+ } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
+ return AppColors.errorColor;
+ } else {
+ return Colors.black;
+ }
+ }
+
void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) {
bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
@@ -210,7 +955,8 @@ class _SelectClinicPageState extends State {
if (clinic.isLiveCareClinicAndOnline ?? false) {
Navigator.of(context).push(
CustomPageRoute(
- page: SelectLivecareClinicPage(onNegativeClicked: (){
+ page: SelectLivecareClinicPage(
+ onNegativeClicked: () {
handleDoctorScreen(clinic);
},
selectedClinic: clinic,
@@ -228,9 +974,7 @@ class _SelectClinicPageState extends State {
//Dental Clinic Flow
if (clinic.clinicID == 17) {
if (appState.isAuthenticated) {
- initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel
- .currentlySelectedHospitalFromRegionFlow ??
- "0"));
+ initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0"));
return;
} else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
@@ -241,8 +985,7 @@ class _SelectClinicPageState extends State {
);
return;
}
- }
- else if (clinic.clinicID == 253) {
+ } else if (clinic.clinicID == 253) {
bookAppointmentsViewModel.resetLaserData();
bookAppointmentsViewModel.getLaserClinic();
Navigator.push(
@@ -269,8 +1012,7 @@ class _SelectClinicPageState extends State {
}
}
- void openRegionListBottomSheet(
- BuildContext context, RegionBottomSheetType type) {
+ void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) {
bookAppointmentsViewModel.setProjectID(null);
regionalViewModel.flush();
@@ -278,12 +1020,11 @@ class _SelectClinicPageState extends State {
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer(builder: (_, data, __) {
- return getRegionalSelectionWidget(data);
- }), callBackFunc: () {
- });
+ return getRegionalSelectionWidget(data);
+ }), callBackFunc: () {});
}
- Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
+ Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
return RegionBottomSheetBody();
}
@@ -308,7 +1049,7 @@ class _SelectClinicPageState extends State {
if (appState.isAuthenticated) {
initDentalAppointment();
return SizedBox.shrink();
- }else {
+ } else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
}
}
@@ -321,8 +1062,7 @@ class _SelectClinicPageState extends State {
}
bookAppointmentsViewModel.setProjectID(id);
return SizedBox.shrink();
- }
- else {
+ } else {
return SizedBox.shrink();
}
return SizedBox.shrink();
@@ -476,9 +1216,7 @@ class _SelectClinicPageState extends State {
void initDentalAppointment() async {
await Future.delayed(Duration(milliseconds: 300));
- initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel
- .currentlySelectedHospitalFromRegionFlow ??
- "0"));
+ initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0"));
return;
}
}
diff --git a/lib/presentation/health_calculators/health_calculator_result.dart b/lib/presentation/health_calculators/health_calculator_result.dart
index a0a46ae..e69de29 100644
--- a/lib/presentation/health_calculators/health_calculator_result.dart
+++ b/lib/presentation/health_calculators/health_calculator_result.dart
@@ -1,473 +0,0 @@
-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/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/book_appointments/book_appointments_view_model.dart';
-import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
-import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
-import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
-import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.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/buttons/custom_button.dart';
-import 'package:provider/provider.dart';
-
-import '../../widgets/common_bottom_sheet.dart';
-
-class HealthCalculatorResultPage extends StatelessWidget {
- HealthCalculatorsTypeEnum calculatorType;
- dynamic calculatedResult;
-
- HealthCalculatorResultPage({super.key, required this.calculatorType, this.calculatedResult});
-
- @override
- Widget build(BuildContext context) {
- return CollapsingListView(
- title: "Your ${calculatorType.name.toCamelCase}",
- bottomChild: Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
- padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
- child: CustomButton(
- text: LocaleKeys.bookAppo.tr(),
- onPressed: () {
- // Navigate to booking page
- var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC;
- openRegionListBottomSheet(context, bottomSheetType);
- getIt.get().setSelectedClinic(
- GetClinicsListResponseModel(clinicID: 108, clinicDescription: "Bariatics", clinicDescriptionN: "Bariatics"),
- );
- getIt.get().setIsDoctorsListLoading(true);
- },
- icon: null,
- fontSize: 16.f,
- backgroundColor: AppColors.primaryRedColor,
- borderColor: AppColors.primaryRedColor,
- borderRadius: 12.r,
- fontWeight: FontWeight.w500),
- ),
- child: getCalculatorResultWidget(type: calculatorType, calculatedResult: calculatedResult).paddingSymmetrical(18.w, 24.h),
- );
- }
-
- void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) {
- getIt.get().setProjectID(null);
-
- getIt.get().flush();
- getIt.get().setBottomSheetType(type);
- showCommonBottomSheetWithoutHeight(context,
- title: "",
- titleWidget: Consumer(builder: (_, data, __) => LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600)),
- isDismissible: false, child: Consumer(builder: (_, data, __) {
- return RegionBottomSheetBody();
- }), callBackFunc: () {});
- }
-
- Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
- switch (calculatorType) {
- case HealthCalculatorsTypeEnum.bmi:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.calories:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.bmr:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.idealBodyWeight:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.bodyFat:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.crabsProteinFat:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.ovulation:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- case HealthCalculatorsTypeEnum.deliveryDueDate:
- return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
- }
- }
-
- Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
- print(calculatedResult);
- return Column(
- children: [
- if (type == HealthCalculatorsTypeEnum.bmi) ...[
- if (calculatedResult["bmiCategory"] == "Normal") ...[
- CalorieWidget(
- title: calculatedResult["bmiCategory"],
- calories: calculatedResult["bmiResult"],
- description:
- 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.',
- color: Colors.green,
- icon: Icons.scale,
- hide: true,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[
- CalorieWidget(
- title: calculatedResult["bmiCategory"],
- calories: calculatedResult["bmiResult"],
- description:
- 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.',
- color: Colors.orange,
- hide: true,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[
- CalorieWidget(
- title: calculatedResult["bmiCategory"],
- calories: calculatedResult["bmiResult"],
- description:
- 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.',
- color: Colors.red,
- hide: true,
- icon: Icons.warning_rounded,
- ),
- ]
- ] else if (type == HealthCalculatorsTypeEnum.calories) ...[
- if (calculatedResult["calorieRange"] == "Normal") ...[
- CalorieWidget(
- title: calculatedResult["calorieRange"],
- calories: calculatedResult["calories"],
- description:
- 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.',
- color: Colors.green,
- icon: Icons.scale,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["calorieRange"] == "Low") ...[
- CalorieWidget(
- title: calculatedResult["calorieRange"],
- calories: calculatedResult["calories"],
- description:
- 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.',
- color: Colors.orange,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["calorieRange"] == "High") ...[
- CalorieWidget(
- title: calculatedResult["calorieRange"],
- calories: calculatedResult["calories"],
- description:
- 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.',
- color: Colors.red,
- icon: Icons.warning_rounded,
- ),
- ]
- ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[
- if (calculatedResult["bmrRange"] == "Normal") ...[
- CalorieWidget(
- title: calculatedResult["bmrRange"],
- calories: calculatedResult["bmr"],
- description:
- 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.',
- color: Colors.green,
- hide: true,
- icon: Icons.scale,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["bmrRange"] == "Low") ...[
- CalorieWidget(
- title: calculatedResult["bmrRange"],
- calories: calculatedResult["bmr"],
- description:
- 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.',
- color: Colors.orange,
- hide: true,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["bmrRange"] == "High") ...[
- CalorieWidget(
- title: calculatedResult["bmrRange"],
- calories: calculatedResult["bmr"],
- description:
- 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.',
- color: Colors.red,
- hide: true,
- icon: Icons.warning_rounded,
- ),
- ]
- ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[
- if (calculatedResult["status"] == "Normal") ...[
- CalorieWidget(
- title: calculatedResult["status"],
- calories: calculatedResult["ibw"],
- hide: true,
- description: (() {
- final diff = calculatedResult["difference"];
- if (diff is num) {
- if (diff == 0) {
- return 'Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.';
- } else if (diff > 0) {
- return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.';
- } else {
- return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.';
- }
- }
- return 'Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.';
- })(),
- color: Colors.green,
- icon: Icons.scale,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["status"] == "Underweight") ...[
- CalorieWidget(
- title: calculatedResult["status"],
- calories: calculatedResult["ibw"],
- hide: true,
- description: (() {
- final diff = calculatedResult["difference"];
- if (diff is num) {
- return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.';
- }
- return 'You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.';
- })(),
- color: Colors.orange,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["status"] == "Overweight") ...[
- CalorieWidget(
- title: calculatedResult["status"],
- calories: calculatedResult["ibw"],
- hide: true,
- description: (() {
- final diff = calculatedResult["difference"];
- if (diff is num) {
- return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.';
- }
- return 'You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.';
- })(),
- color: Colors.red,
- icon: Icons.warning_rounded,
- ),
- ]
- ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[
- if (calculatedResult["dietType"] == "Very Low Crabs") ...[
- CalorieWidget(
- title: calculatedResult["dietType"].toString(),
- calories: calculatedResult["totalCalories"],
- description: (() {
- final total = calculatedResult["totalCalories"];
- final carbsG = calculatedResult["carbsGrams"];
- final carbsC = calculatedResult["carbsCalories"];
- final protG = calculatedResult["proteinGrams"];
- final protC = calculatedResult["proteinCalories"];
- final fatG = calculatedResult["fatGrams"];
- final fatC = calculatedResult["fatCalories"];
- final base =
- 'Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.';
- if (total is num) {
- return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
- }
- return base;
- })(),
- color: Colors.orange,
- hide: true,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["dietType"] == "Low Crabs") ...[
- CalorieWidget(
- title: calculatedResult["dietType"].toString(),
- calories: calculatedResult["totalCalories"],
- description: (() {
- final total = calculatedResult["totalCalories"];
- final carbsG = calculatedResult["carbsGrams"];
- final carbsC = calculatedResult["carbsCalories"];
- final protG = calculatedResult["proteinGrams"];
- final protC = calculatedResult["proteinCalories"];
- final fatG = calculatedResult["fatGrams"];
- final fatC = calculatedResult["fatCalories"];
- final base =
- 'Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.';
- if (total is num) {
- return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
- }
- return base;
- })(),
- color: Colors.orange,
- hide: true,
- icon: Icons.trending_down,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[
- CalorieWidget(
- title: calculatedResult["dietType"].toString(),
- calories: calculatedResult["totalCalories"],
- description: (() {
- final total = calculatedResult["totalCalories"];
- final carbsG = calculatedResult["carbsGrams"];
- final carbsC = calculatedResult["carbsCalories"];
- final protG = calculatedResult["proteinGrams"];
- final protC = calculatedResult["proteinCalories"];
- final fatG = calculatedResult["fatGrams"];
- final fatC = calculatedResult["fatCalories"];
- final base =
- 'Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.';
- if (total is num) {
- return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
- }
- return base;
- })(),
- color: Colors.green,
- hide: true,
- icon: Icons.scale,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[
- CalorieWidget(
- title: calculatedResult["dietType"].toString(),
- calories: calculatedResult["totalCalories"],
- description: (() {
- final total = calculatedResult["totalCalories"];
- final carbsG = calculatedResult["carbsGrams"];
- final carbsC = calculatedResult["carbsCalories"];
- final protG = calculatedResult["proteinGrams"];
- final protC = calculatedResult["proteinCalories"];
- final fatG = calculatedResult["fatGrams"];
- final fatC = calculatedResult["fatCalories"];
- final base =
- 'USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.';
- if (total is num) {
- return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
- }
- return base;
- })(),
- color: Colors.green,
- hide: true,
- icon: Icons.scale,
- ),
- SizedBox(height: 16.h),
- ] else if (calculatedResult["dietType"] == "Zone Diet") ...[
- CalorieWidget(
- title: calculatedResult["dietType"].toString(),
- calories: calculatedResult["totalCalories"],
- description: (() {
- final total = calculatedResult["totalCalories"];
- final carbsG = calculatedResult["carbsGrams"];
- final carbsC = calculatedResult["carbsCalories"];
- final protG = calculatedResult["proteinGrams"];
- final protC = calculatedResult["proteinCalories"];
- final fatG = calculatedResult["fatGrams"];
- final fatC = calculatedResult["fatCalories"];
- final base =
- 'Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.';
- if (total is num) {
- return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
- }
- return base;
- })(),
- color: Colors.orange,
- hide: true,
- icon: Icons.warning_rounded,
- ),
- ]
- ]
- ],
- );
- }
-}
-
-class CalorieWidget extends StatelessWidget {
- final String title;
- final dynamic calories;
- final String description;
- final Color color;
- final IconData icon;
- final bool hide;
-
- const CalorieWidget({super.key, required this.title, required this.calories, required this.description, required this.color, required this.icon, this.hide = false});
-
- @override
- Widget build(BuildContext context) {
- return Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
- padding: EdgeInsets.all(16.w),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Row(
- children: [
- ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(), isBold: true),
- SizedBox(width: 10.w),
- hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600)
- ],
- ),
- _buildStatusIcon()
- ],
- ),
- SizedBox(height: 16.h),
- title.toText16(color: AppColors.textColor, weight: FontWeight.w600),
- SizedBox(height: 8.h),
- Text(
- description,
- style: TextStyle(
- fontSize: 14,
- color: Colors.grey[700],
- height: 1.5,
- ),
- textAlign: TextAlign.start,
- ),
- ],
- ),
- );
- }
- //
- // Widget _buildStatusIcon() {
- // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') {
- // return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
- // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') {
- // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
- // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") {
- // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
- // } else {
- // return SizedBox();
- // }
- // }
- //
- // Color getTextColor() {
- // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') {
- // return AppColors.successColor;
- // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') {
- // return AppColors.warningColor;
- // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") {
- // return AppColors.errorColor;
- // } else {
- // return Colors.black;
- // }
- // }
-
- Widget _buildStatusIcon() {
- final key = title.trim();
- if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
- return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
- } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
- return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
- } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
- return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
- } else {
- return SizedBox();
- }
- }
-
- Color getTextColor() {
- final key = title.trim();
- if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
- return AppColors.successColor;
- } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
- return AppColors.warningColor;
- } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
- return AppColors.errorColor;
- } else {
- return Colors.black;
- }
- }
-}
diff --git a/lib/presentation/health_calculators/health_calculators_page.dart b/lib/presentation/health_calculators/health_calculators_page.dart
deleted file mode 100644
index 8ac7a22..0000000
--- a/lib/presentation/health_calculators/health_calculators_page.dart
+++ /dev/null
@@ -1,157 +0,0 @@
-import 'dart:async';
-
-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_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/presentation/health_calculators/health_calculator_detailed_page.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/health_card.dart';
-import 'package:hmg_patient_app_new/services/dialog_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/routes/custom_page_route.dart';
-
-class HealthCalculatorsPage extends StatefulWidget {
- const HealthCalculatorsPage({super.key});
-
- @override
- State createState() => _HealthCalculatorsPageState();
-}
-
-class _HealthCalculatorsPageState extends State {
- @override
- void initState() {
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- DialogService dialogService = getIt.get();
- return CollapsingListView(
- title: "Health Calculators".needTranslation,
- child: Column(
- children: [
- Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
- child: Row(
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none),
- SizedBox(width: 12.w),
- Flexible(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- "General Health".needTranslation.toText16(weight: FontWeight.w600),
- "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
- ],
- ),
- ),
- SizedBox(
- width: 12.w,
- ),
- Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
- ],
- ).paddingAll(16.w))
- .onPress(() {
- dialogService.showFamilyBottomSheetWithoutHWithChild(
- label: "Select Calculator".needTranslation,
- message: "",
- child: showCalculatorsItems(type: HealthCalculatorEnum.general),
- onOkPressed: () {},
- );
- }),
- SizedBox(height: 16.h),
- Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
- child: Row(
- children: [
- Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none),
- SizedBox(width: 12.w),
- Flexible(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- "Women's Health".needTranslation.toText16(weight: FontWeight.w600),
- "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
- ],
- ),
- ),
- SizedBox(width: 12.w),
- Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
- ],
- ).paddingAll(16.w))
- .onPress(() {
- dialogService.showFamilyBottomSheetWithoutHWithChild(
- label: "Select Calculator".needTranslation,
- message: "",
- child: showCalculatorsItems(type: HealthCalculatorEnum.women),
- onOkPressed: () {},
- );
- }),
- ],
- ).paddingSymmetrical(20.w, 24.h));
- }
-
- Widget showCalculatorsItems({required HealthCalculatorEnum type}) {
- return GridView.builder(
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3, // 4 icons per row
- crossAxisSpacing: 16.w,
- mainAxisSpacing: 16.w,
- childAspectRatio: 0.80),
- physics: NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length,
- padding: EdgeInsets.zero,
- itemBuilder: (BuildContext context, int index) {
- return HealthCard(
- icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon,
- labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title,
- onTap: () {
- Navigator.of(context).push(
- CustomPageRoute(
- page: HealthCalculatorDetailedPage(
- calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
- ),
- ),
- );
- },
- );
- },
- );
- }
-
- final List generalHealthServices = [
- HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi),
- HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories),
- HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr),
- HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight),
- HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat),
- HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat),
- ];
-
- final List womenHealthServices = [
- HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation),
- HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate),
- ];
-}
-
-class HealthComponentModel {
- String title;
- String? subTitle;
- String icon;
- Color? iconColor;
- Color? bgColor;
- Color? textColor;
- HealthCalculatorsTypeEnum type;
-
- HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type});
-}
diff --git a/lib/presentation/health_calculators/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart
similarity index 61%
rename from lib/presentation/health_calculators/health_calculator_detailed_page.dart
rename to lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart
index ae7d46b..e0f3a7a 100644
--- a/lib/presentation/health_calculators/health_calculator_detailed_page.dart
+++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart
@@ -1,25 +1,28 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_sugar.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmi.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmr.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/calories.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/crabs.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/dduedate.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart';
import 'package:provider/provider.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/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_result.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bf.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmi.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmr.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/calories.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/crabs.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/dduedate.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ibw.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ovulation.dart';
import 'package:hmg_patient_app_new/services/dialog_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/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
class HealthCalculatorDetailedPage extends StatefulWidget {
HealthCalculatorsTypeEnum calculatorType;
@@ -40,24 +43,34 @@ class _HealthCalculatorDetailedPageState extends State(builder: (context, provider, _) {
return CollapsingListView(
title: widget.calculatorType.displayName,
- bottomChild: Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
- padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
- child: CustomButton(
- text: "Calculate".needTranslation,
- onPressed: () {
- if (calculatedResult == null) return;
- DialogService dialogService = getIt.get();
- Navigator.of(context).push(CustomPageRoute(
- page: HealthCalculatorResultPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
- ));
- },
- icon: null,
- fontSize: 16.f,
- backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
- borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
- borderRadius: 12.r,
- fontWeight: FontWeight.w500)),
+ bottomChild: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
+ widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
+ widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
+ ? SizedBox()
+ : Container(
+ decoration:
+ RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
+ padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
+ child: CustomButton(
+ text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
+ widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
+ widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
+ ? "Convert".needTranslation
+ : "Calculate".needTranslation,
+ onPressed: () {
+ if (calculatedResult == null) return;
+
+ DialogService dialogService = getIt.get();
+ Navigator.of(context).push(CustomPageRoute(
+ page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
+ ));
+ },
+ icon: null,
+ fontSize: 16.f,
+ backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
+ borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
+ borderRadius: 12.r,
+ fontWeight: FontWeight.w500)),
child: getCalculatorWidget(
type: widget.calculatorType,
onCalculate: (result) {
@@ -85,6 +98,12 @@ class _HealthCalculatorDetailedPageState extends State? deliveryResult;
+ Map? bloodSugarResult;
+ Map? bloodCholesterolResult;
+ Map? triglyceridesResult;
+
+ String? _mgdlValue;
+
+ String? get mgdlValue => _mgdlValue;
+
+ String? _mmolValue;
+
+ String? get mmolValue => _mmolValue;
+
+ String _activeUnit = 'mg/dL'; // current source unit
+ String get activeUnit => _activeUnit;
+
+
+ // ================== BLOOD CHOLESTEROL ==================
+ String? _cholMgdlValue;
+ String? _cholMmolValue;
+ String _cholActiveUnit = 'mg/dL';
+
+ String? get cholMgdlValue => _cholMgdlValue;
+ String? get cholMmolValue => _cholMmolValue;
+ String get cholActiveUnit => _cholActiveUnit;
+
+ // ================== TRIGLYCERIDES ==================
+ String? _triMgdlValue;
+ String? _triMmolValue;
+ String _triActiveUnit = 'mg/dL';
+
+ String? get triMgdlValue => _triMgdlValue;
+ String? get triMmolValue => _triMmolValue;
+ String get triActiveUnit => _triActiveUnit;
+
+
// Generic helpers
void calculateBMI({required String heightText, required String weightText, required String heightUnit, required String weightUnit}) {
@@ -113,7 +148,15 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
// BMR / Calories (shared logic)
- void calculateBMRAndCalories({required String heightText, required String weightText, required String ageText, required String heightUnit, required String weightUnit, required String gender, required String activityLevel, bool forCaloriesOnly = false}) {
+ void calculateBMRAndCalories(
+ {required String heightText,
+ required String weightText,
+ required String ageText,
+ required String heightUnit,
+ required String weightUnit,
+ required String gender,
+ required String activityLevel,
+ bool forCaloriesOnly = false}) {
if (heightText.trim().isEmpty || weightText.trim().isEmpty || ageText.trim().isEmpty) {
bmrResult = null;
caloriesResult = null;
@@ -303,7 +346,16 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
// Body Fat
- void calculateBodyFat({required String heightText, required String neckText, required String waistText, required String hipText, required String heightUnit, required String neckUnit, required String waistUnit, required String hipUnit, required String gender}) {
+ void calculateBodyFat(
+ {required String heightText,
+ required String neckText,
+ required String waistText,
+ required String hipText,
+ required String heightUnit,
+ required String neckUnit,
+ required String waistUnit,
+ required String hipUnit,
+ required String gender}) {
if (heightText.trim().isEmpty || neckText.trim().isEmpty || waistText.trim().isEmpty) {
bodyFatResult = null;
bodyFatCategory = null;
@@ -548,6 +600,29 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
return months[month - 1];
}
+ String _getDayName(int weekday) {
+ // DateTime.weekday returns 1 for Monday and 7 for Sunday.
+ const List days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
+ return days[weekday - 1];
+ }
+
+ String _formatDateWithDayName(DateTime? date) {
+ if (date == null) return '';
+ String dayName = _getDayName(date.weekday); // Gets 'Monday', 'Tuesday', etc.
+ return dayName;
+ }
+
+ Map convertBloodSugar(double value, String fromUnit) {
+ if (fromUnit == 'mg/dL') {
+ final mmolL = value / 18.0182;
+ return {'mgdL': value, 'mmolL': mmolL};
+ } else if (fromUnit == 'mmol/L') {
+ final mgdL = value * 18.0182;
+ return {'mgdL': mgdL, 'mmolL': value};
+ }
+ return {'mgdL': 0, 'mmolL': 0};
+ }
+
String _determineCyclePhase(int daysSincePeriod, int follicularPhase, int cycleLength) {
int normalizedDay = daysSincePeriod % cycleLength;
@@ -600,6 +675,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
String cyclePhase = _determineCyclePhase(daysSincePeriod, follicularPhase, cycleLength);
ovulationResult = {
+ 'lastPeriodDay': _formatDateWithDayName(parsedDate),
+ 'ovulationDay': _formatDateWithDayName(ovulationDate),
'lastPeriodDate': _formatDate(parsedDate),
'ovulationDate': _formatDate(ovulationDate),
'fertileWindowStart': _formatDate(fertileStart),
@@ -629,7 +706,7 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
DateTime? lmp;
try {
lmp = DateTime.tryParse(isoDateString) ?? _tryParseDate(isoDateString);
- } catch (e) {
+ } catch (_) {
lmp = null;
}
@@ -639,19 +716,293 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
return;
}
- DateTime dueDate = lmp.add(Duration(days: 280));
- String dueDateStr = _formatDate(dueDate);
- String lmpStr = _formatDate(lmp);
+ // Pregnancy constants
+ const int totalPregnancyDays = 280;
+
+ // Calculate main dates
+ final DateTime dueDate = lmp.add(const Duration(days: totalPregnancyDays));
+
+ // Trimester calculations
+ final DateTime firstTrimesterStart = lmp;
+ final DateTime firstTrimesterEnd = lmp.add(const Duration(days: 83));
+
+ final DateTime secondTrimesterStart =
+ firstTrimesterEnd.add(const Duration(days: 1));
+ final DateTime secondTrimesterEnd =
+ lmp.add(const Duration(days: 195));
+
+ final DateTime thirdTrimesterStart =
+ secondTrimesterEnd.add(const Duration(days: 1));
+ final DateTime thirdTrimesterEnd = dueDate;
+
+ deliveryResult = {
+ 'lmpDate': _formatDate(lmp),
+ 'dueDate': _formatDate(dueDate),
+ // Raw DateTime (useful for logic/UI)
+ 'lmpDateTime': lmp,
+ 'dueDateTime': dueDate,
+ 'dueDateDay' : _formatDateWithDayName(dueDate),
+ // Trimester info
+ 'firstTrimester': {
+ 'start': _formatDate(firstTrimesterStart),
+ 'end': _formatDate(firstTrimesterEnd),
+ 'weeks': '1–12',
+ 'startDateTime': firstTrimesterStart,
+ 'endDateTime': firstTrimesterEnd,
+ },
+ 'secondTrimester': {
+ 'start': _formatDate(secondTrimesterStart),
+ 'end': _formatDate(secondTrimesterEnd),
+ 'weeks': '13–27',
+ 'startDateTime': secondTrimesterStart,
+ 'endDateTime': secondTrimesterEnd,
+ },
+ 'thirdTrimester': {
+ 'start': _formatDate(thirdTrimesterStart),
+ 'end': _formatDate(thirdTrimesterEnd),
+ 'weeks': '28–40',
+ 'startDateTime': thirdTrimesterStart,
+ 'endDateTime': thirdTrimesterEnd,
+ },
+ };
- deliveryResult = {'lmpDate': lmpStr, 'dueDate': dueDateStr, 'lmpDateTime': lmp, 'dueDateTime': dueDate};
notifyListeners();
}
+
+ // Blood sugar conversions
+ void calculateBloodSugar({required String valueText, required String unit}) {
+ if (valueText.trim().isEmpty) {
+ bloodSugarResult = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double value = double.parse(valueText.trim());
+
+ if (value <= 0) {
+ bloodSugarResult = null;
+ notifyListeners();
+ return;
+ }
+
+ double converted;
+ String convertedUnit;
+
+ if (unit == 'mg/dL' || unit == 'MG/DL' || unit.toLowerCase().contains('mg')) {
+ // mg/dL -> mmol/L (glucose): divide by 18.0182
+ converted = value / 18.0182;
+ convertedUnit = 'mmol/L';
+ } else {
+ // mmol/L -> mg/dL
+ converted = value * 18.0182;
+ convertedUnit = 'mg/dL';
+ }
+
+ // round to sensible precision
+ double convertedRounded = double.parse(converted.toStringAsFixed(2));
+
+ bloodSugarResult = {
+ 'input': value,
+ 'inputUnit': unit,
+ 'converted': convertedRounded,
+ 'convertedUnit': convertedUnit,
+ };
+ notifyListeners();
+ } catch (e) {
+ bloodSugarResult = null;
+ notifyListeners();
+ }
+ }
+
// expose map-like results for widgets to forward to parent
Map? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory};
+
Map? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
+
Map? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus};
+
Map? get bodyFatResultMap => bodyFatResult == null ? null : {'fatPercentage': bodyFatResult, 'fatCategory': bodyFatCategory};
- Map? get macrosResultMap => carbsCalories == null ? null : {'totalCalories': carbsCalories! + proteinCalories! + fatCalories!, 'carbsGrams': carbsGrams, 'carbsCalories': carbsCalories, 'proteinGrams': proteinGrams, 'proteinCalories': proteinCalories, 'fatGrams': fatGrams, 'fatCalories': fatCalories, 'dietType': dietType};
-}
+ Map? get macrosResultMap => carbsCalories == null
+ ? null
+ : {
+ 'totalCalories': carbsCalories! + proteinCalories! + fatCalories!,
+ 'carbsGrams': carbsGrams,
+ 'carbsCalories': carbsCalories,
+ 'proteinGrams': proteinGrams,
+ 'proteinCalories': proteinCalories,
+ 'fatGrams': fatGrams,
+ 'fatCalories': fatCalories,
+ 'dietType': dietType
+ };
+
+
+
+ void onBloodSugarChanged(String value, String fromUnit) {
+ _activeUnit = fromUnit;
+
+ if (value.isEmpty) {
+ _mgdlValue = null;
+ _mmolValue = null;
+ notifyListeners();
+ return;
+ }
+
+ final parsed = double.tryParse(value);
+ if (parsed == null) return;
+
+ if (fromUnit == 'mg/dL') {
+ _mgdlValue = value;
+ _mmolValue = (parsed / 18.0182).toStringAsFixed(1);
+ } else {
+ _mmolValue = value;
+ _mgdlValue = (parsed * 18.0182).toStringAsFixed(0);
+ }
+
+ notifyListeners();
+ }
+
+ void switchBloodSugarValues() {
+ if (_activeUnit == 'mg/dL') {
+ final mmol = double.tryParse(_mmolValue ?? '');
+ if (mmol == null) return;
+
+ _activeUnit = 'mmol/L';
+
+ // mmol stays as source
+ _mmolValue = mmol.toStringAsFixed(1);
+ _mgdlValue = (mmol * 18.0182).toStringAsFixed(0);
+ } else {
+ final mgdl = double.tryParse(_mgdlValue ?? '');
+ if (mgdl == null) return;
+
+ _activeUnit = 'mg/dL';
+
+ // mg/dL stays as source
+ _mgdlValue = mgdl.toStringAsFixed(0);
+ _mmolValue = (mgdl / 18.0182).toStringAsFixed(1);
+ }
+
+ notifyListeners();
+ }
+ // --- NEW: Method to clear the values ---
+ void clearBloodSugar() {
+ _mgdlValue = null;
+ _mmolValue = null;
+ notifyListeners();
+ }
+
+
+
+ void onBloodCholesterolChanged(String value, String fromUnit) {
+ _cholActiveUnit = fromUnit;
+
+ if (value.isEmpty) {
+ _cholMgdlValue = null;
+ _cholMmolValue = null;
+ notifyListeners();
+ return;
+ }
+
+ final parsed = double.tryParse(value);
+ if (parsed == null) return;
+
+ if (fromUnit == 'mg/dL') {
+ _cholMgdlValue = value;
+ _cholMmolValue = (parsed / 38.67).toStringAsFixed(2);
+ } else {
+ _cholMmolValue = value;
+ _cholMgdlValue = (parsed * 38.67).toStringAsFixed(0);
+ }
+
+ notifyListeners();
+ }
+
+ void switchBloodCholesterolValues() {
+ if (_cholActiveUnit == 'mg/dL') {
+ final mmol = double.tryParse(_cholMmolValue ?? '');
+ if (mmol == null) return;
+
+ _cholActiveUnit = 'mmol/L';
+
+ _cholMmolValue = mmol.toStringAsFixed(2);
+ _cholMgdlValue = (mmol * 38.67).toStringAsFixed(0);
+ } else {
+ final mgdl = double.tryParse(_cholMgdlValue ?? '');
+ if (mgdl == null) return;
+
+ _cholActiveUnit = 'mg/dL';
+
+ _cholMgdlValue = mgdl.toStringAsFixed(0);
+ _cholMmolValue = (mgdl / 38.67).toStringAsFixed(2);
+ }
+
+ notifyListeners();
+ }
+
+
+ void clearBloodCholesterol() {
+ _cholMgdlValue = null;
+ _cholMmolValue = null;
+ _cholActiveUnit = 'mg/dL';
+ notifyListeners();
+ }
+
+
+
+ void onTriglyceridesChanged(String value, String fromUnit) {
+ _triActiveUnit = fromUnit;
+
+ if (value.isEmpty) {
+ _triMgdlValue = null;
+ _triMmolValue = null;
+ notifyListeners();
+ return;
+ }
+
+ final parsed = double.tryParse(value);
+ if (parsed == null) return;
+
+ if (fromUnit == 'mg/dL') {
+ _triMgdlValue = value;
+ _triMmolValue = (parsed / 88.57).toStringAsFixed(2);
+ } else {
+ _triMmolValue = value;
+ _triMgdlValue = (parsed * 88.57).toStringAsFixed(0);
+ }
+
+ notifyListeners();
+ }
+
+ void switchTriglyceridesValues() {
+ if (_triActiveUnit == 'mg/dL') {
+ final mmol = double.tryParse(_triMmolValue ?? '');
+ if (mmol == null) return;
+
+ _triActiveUnit = 'mmol/L';
+ _triMmolValue = mmol.toStringAsFixed(2);
+ _triMgdlValue = (mmol * 88.57).toStringAsFixed(0);
+ } else {
+ final mgdl = double.tryParse(_triMgdlValue ?? '');
+ if (mgdl == null) return;
+
+ _triActiveUnit = 'mg/dL';
+ _triMgdlValue = mgdl.toStringAsFixed(0);
+ _triMmolValue = (mgdl / 88.57).toStringAsFixed(2);
+ }
+
+ notifyListeners();
+ }
+
+ void clearTriglycerides() {
+ _triMgdlValue = null;
+ _triMmolValue = null;
+ _triActiveUnit = 'mg/dL';
+ notifyListeners();
+ }
+
+
+
+}
diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart
new file mode 100644
index 0000000..702e04c
--- /dev/null
+++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart
@@ -0,0 +1,255 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.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/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/health_card.dart';
+import 'package:hmg_patient_app_new/services/dialog_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/routes/custom_page_route.dart';
+
+class HealthCalculatorsPage extends StatefulWidget {
+ HealthCalConEnum type;
+
+ HealthCalculatorsPage({super.key, required this.type});
+
+ @override
+ State createState() => _HealthCalculatorsPageState();
+}
+
+class _HealthCalculatorsPageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt.get();
+ return CollapsingListView(
+ title: widget.type == HealthCalConEnum.calculator ? "Health Calculators".needTranslation : "Health Converters".needTranslation,
+ child: widget.type == HealthCalConEnum.calculator
+ ? Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "General Health".needTranslation.toText16(weight: FontWeight.w600),
+ "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(
+ width: 12.w,
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Calculator".needTranslation,
+ message: "",
+ child: showCalculatorsItems(type: HealthCalculatorEnum.general),
+ onOkPressed: () {},
+ );
+ }),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "Women's Health".needTranslation.toText16(weight: FontWeight.w600),
+ "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Calculator".needTranslation,
+ message: "",
+ child: showCalculatorsItems(type: HealthCalculatorEnum.women),
+ onOkPressed: () {},
+ );
+ }),
+ ],
+ ).paddingSymmetrical(20.w, 24.h)
+ : Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "Blood Sugar".needTranslation.toText16(weight: FontWeight.w600),
+ "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation.toText12(
+ fontWeight: FontWeight.w500,
+ color: Color(0xFF8F9AA3),
+ )
+ ],
+ ),
+ ),
+ SizedBox(
+ width: 12.w,
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodSugar),
+ ),
+ );
+ }),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "Blood Cholesterol".needTranslation.toText16(weight: FontWeight.w600),
+ "Monitor your cholesterol levels, track your LDL, HDL, and triglycerides. Get personalized recommendations for a healthy heart."
+ .needTranslation
+ .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodCholesterol),
+ ),
+ );
+ }),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "Triglycerides Fat Blood".needTranslation.toText16(weight: FontWeight.w600),
+ "Manage triglycerides, a key blood fat. Understand levels, diet impacts, and heart health strategies."
+ .needTranslation
+ .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.triglycerides),
+ ),
+ );
+ }),
+ ],
+ ).paddingSymmetrical(20.w, 24.h));
+ }
+
+ Widget showCalculatorsItems({required HealthCalculatorEnum type}) {
+ return GridView.builder(
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 3, // 4 icons per row
+ crossAxisSpacing: 16.w,
+ mainAxisSpacing: 16.w,
+ childAspectRatio: 0.80),
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length,
+ padding: EdgeInsets.zero,
+ itemBuilder: (BuildContext context, int index) {
+ return HealthCard(
+ icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon,
+ labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title,
+ onTap: () {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(
+ calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
+ ),
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ final List generalHealthServices = [
+ HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi),
+ HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories),
+ HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr),
+ HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight),
+ HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat),
+ HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat),
+ ];
+
+ final List womenHealthServices = [
+ HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation),
+ HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate),
+ ];
+}
+
+class HealthComponentModel {
+ String title;
+ String? subTitle;
+ String icon;
+ Color? iconColor;
+ Color? bgColor;
+ Color? textColor;
+ HealthCalculatorsTypeEnum type;
+
+ HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type});
+}
diff --git a/lib/presentation/health_calculators/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart
similarity index 99%
rename from lib/presentation/health_calculators/widgets/bf.dart
rename to lib/presentation/health_calculators_and_converts/widgets/bf.dart
index bbab9a5..3a37036 100644
--- a/lib/presentation/health_calculators/widgets/bf.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'dart:math';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@@ -9,7 +10,6 @@ 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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
class BodyFatWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart
new file mode 100644
index 0000000..161f318
--- /dev/null
+++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart
@@ -0,0 +1,180 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+
+class BloodCholesterolWidget extends StatefulWidget {
+ final Function(dynamic result)? onChange;
+
+ const BloodCholesterolWidget({super.key, this.onChange});
+
+ @override
+ State createState() => _BloodCholesterolWidgetState();
+}
+
+class _BloodCholesterolWidgetState extends State {
+ final TextEditingController _mgdlController = TextEditingController();
+ final TextEditingController _mmolController = TextEditingController();
+ final FocusNode _mgdlFocus = FocusNode();
+ final FocusNode _mmolFocus = FocusNode();
+ bool _isProgrammaticChange = false;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Provider.of(
+ context,
+ listen: false,
+ ).clearBloodCholesterol();
+ });
+ }
+
+ @override
+ void dispose() {
+ _mgdlController.dispose();
+ _mmolController.dispose();
+ _mgdlFocus.dispose();
+ _mmolFocus.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (context, provider, _) {
+ _isProgrammaticChange = true;
+ final mgdlText = provider.cholMgdlValue ?? '';
+ final mmolText = provider.cholMmolValue ?? '';
+
+ if (_mgdlController.text != mgdlText) {
+ _mgdlController.text = mgdlText;
+ _mgdlController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mgdlController.text.length),
+ );
+ }
+
+ if (_mmolController.text != mmolText) {
+ _mmolController.text = mmolText;
+ _mmolController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mmolController.text.length),
+ );
+ }
+
+ _isProgrammaticChange = false;
+
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ _buildInputField(
+ label: "MG/DL",
+ hint: "120",
+ controller: _mgdlController,
+ focusNode: _mgdlFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+ provider.onBloodCholesterolChanged(value, 'mg/dL');
+ },
+ ).paddingOnly(top: 16.h),
+ Row(
+ children: [
+ const Expanded(
+ flex: 3,
+ child: Divider(height: 1, color: Color(0xFFEEEEEE)),
+ ),
+ SizedBox(width: 8.w),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.switchBtn,
+ width: 40.h,
+ height: 40.h,
+ ).onPress(() {
+ provider.switchBloodCholesterolValues();
+ if (provider.cholActiveUnit == 'mg/dL') {
+ _mgdlFocus.requestFocus();
+ } else {
+ _mmolFocus.requestFocus();
+ }
+ }),
+ ],
+ ),
+ _buildInputField(
+ label: "MMOL/L",
+ hint: "6.7",
+ controller: _mmolController,
+ focusNode: _mmolFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+
+ provider.onBloodCholesterolChanged(value, 'mmol/L');
+ },
+ ).paddingOnly(bottom: 16.h),
+ const Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ /// Reusable Input Field
+ Widget _buildInputField({
+ required String label,
+ required String hint,
+ required TextEditingController controller,
+ required FocusNode focusNode,
+ required ValueChanged onChanged,
+ }) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ label.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.inputLabelTextColor,
+ ),
+ SizedBox(
+ height: 40.h,
+ child: TextField(
+ controller: controller,
+ focusNode: focusNode,
+ keyboardType: const TextInputType.numberWithOptions(decimal: true),
+ maxLines: 1,
+ onChanged: onChanged,
+ cursorHeight: 35.h,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: hint,
+ hintStyle: const TextStyle(color: Colors.grey),
+ ),
+ style: TextStyle(
+ fontSize: 32.f,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.h,
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart
new file mode 100644
index 0000000..86b4c1e
--- /dev/null
+++ b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart
@@ -0,0 +1,190 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+
+class BloodSugarWidget extends StatefulWidget {
+ final Function(dynamic result)? onChange;
+
+ const BloodSugarWidget({super.key, this.onChange});
+
+ @override
+ State createState() => _BloodSugarWidgetState();
+}
+
+class _BloodSugarWidgetState extends State {
+ final TextEditingController _mgdlController = TextEditingController();
+ final TextEditingController _mmolController = TextEditingController();
+ final FocusNode _mgdlFocus = FocusNode();
+ final FocusNode _mmolFocus = FocusNode();
+ bool _isProgrammaticChange = false;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Provider.of(
+ context,
+ listen: false,
+ ).clearBloodSugar();
+ });
+ }
+
+ @override
+ void dispose() {
+ _mgdlController.dispose();
+ _mmolController.dispose();
+ _mgdlFocus.dispose();
+ _mmolFocus.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (context, provider, _) {
+ _isProgrammaticChange = true;
+ final mgdlText = provider.mgdlValue ?? '';
+ final mmolText = provider.mmolValue ?? '';
+ if (_mgdlController.text != mgdlText) {
+ _mgdlController.text = mgdlText;
+ _mgdlController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mgdlController.text.length),
+ );
+ }
+
+ if (_mmolController.text != mmolText) {
+ _mmolController.text = mmolText;
+ _mmolController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mmolController.text.length),
+ );
+ }
+
+ _isProgrammaticChange = false;
+
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.r,
+ hasShadow: true,
+ ),
+ child: Column(
+ children: [
+ _buildInputField(
+ label: "MG/DL",
+ hint: "120",
+ controller: _mgdlController,
+ focusNode: _mgdlFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+ provider.onBloodSugarChanged(value, 'mg/dL');
+ },
+ ).paddingOnly(top: 16.h),
+ Row(
+ children: [
+ const Expanded(
+ flex: 3,
+ child: Divider(height: 1, color: Color(0xFFEEEEEE)),
+ ),
+ SizedBox(width: 8.w),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.switchBtn,
+ width: 40.h,
+ height: 40.h,
+ ).onPress(() {
+ provider.switchBloodSugarValues();
+ if (provider.activeUnit == 'mg/dL') {
+ _mgdlFocus.requestFocus();
+ } else {
+ _mmolFocus.requestFocus();
+ }
+ }),
+ ],
+ ),
+ _buildInputField(
+ label: "MMOL/L",
+ hint: "6.7",
+ controller: _mmolController,
+ focusNode: _mmolFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+
+ provider.onBloodSugarChanged(value, 'mmol/L');
+ },
+ ).paddingOnly(bottom: 16.h),
+ const Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.globe,
+ width: 18.w,
+ height: 18.w,
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)".toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.inputLabelTextColor,
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ /// Reusable Input Field
+ Widget _buildInputField({
+ required String label,
+ required String hint,
+ required TextEditingController controller,
+ required FocusNode focusNode,
+ required ValueChanged onChanged,
+ }) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ label.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.inputLabelTextColor,
+ ),
+ SizedBox(
+ height: 40.h,
+ child: TextField(
+ controller: controller,
+ focusNode: focusNode,
+ keyboardType: const TextInputType.numberWithOptions(decimal: true),
+ maxLines: 1,
+ onChanged: onChanged,
+ cursorHeight: 35.h,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: hint,
+ hintStyle: const TextStyle(color: Colors.grey),
+ ),
+ style: TextStyle(
+ fontSize: 32.f,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.h,
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart
similarity index 99%
rename from lib/presentation/health_calculators/widgets/bmi.dart
rename to lib/presentation/health_calculators_and_converts/widgets/bmi.dart
index be87d0d..49b77c0 100644
--- a/lib/presentation/health_calculators/widgets/bmi.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart
@@ -8,7 +8,7 @@ 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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class BMIWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart
similarity index 99%
rename from lib/presentation/health_calculators/widgets/bmr.dart
rename to lib/presentation/health_calculators_and_converts/widgets/bmr.dart
index 771191e..138fd3e 100644
--- a/lib/presentation/health_calculators/widgets/bmr.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart
@@ -10,7 +10,7 @@ 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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class BMRWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart
similarity index 99%
rename from lib/presentation/health_calculators/widgets/calories.dart
rename to lib/presentation/health_calculators_and_converts/widgets/calories.dart
index 0d7aab7..bfaad3e 100644
--- a/lib/presentation/health_calculators/widgets/calories.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart
@@ -10,7 +10,7 @@ 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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class CaloriesWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart
similarity index 98%
rename from lib/presentation/health_calculators/widgets/crabs.dart
rename to lib/presentation/health_calculators_and_converts/widgets/crabs.dart
index 3fa1bdf..5fe1215 100644
--- a/lib/presentation/health_calculators/widgets/crabs.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart
@@ -7,11 +7,11 @@ 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/presentation/health_calculators/health_calculator_detailed_page.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class CrabsWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators/widgets/dduedate.dart b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart
similarity index 94%
rename from lib/presentation/health_calculators/widgets/dduedate.dart
rename to lib/presentation/health_calculators_and_converts/widgets/dduedate.dart
index 91057aa..15c0a0c 100644
--- a/lib/presentation/health_calculators/widgets/dduedate.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart
@@ -8,7 +8,7 @@ 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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class DeliveryDueDWidget extends StatefulWidget {
final Function(dynamic result)? onChange;
@@ -35,7 +35,7 @@ class _DeliveryDueDWidgetState extends State {
child: Column(
children: [
TextInputWidget(
- labelText: "Date",
+ labelText: "Last Period Date",
hintText: "11 July, 1994".needTranslation,
controller: _date,
focusNode: FocusNode(),
@@ -56,8 +56,6 @@ class _DeliveryDueDWidgetState extends State {
selectedDate = Utils.formatDateToDisplay(val);
_date.text = selectedDate;
});
-
- // provider expects ISO date string to calculate due date
provider.calculateDueDate(isoDateString: val);
if (widget.onChange != null) {
diff --git a/lib/presentation/health_calculators/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart
similarity index 100%
rename from lib/presentation/health_calculators/widgets/health_card.dart
rename to lib/presentation/health_calculators_and_converts/widgets/health_card.dart
diff --git a/lib/presentation/health_calculators/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart
similarity index 99%
rename from lib/presentation/health_calculators/widgets/ibw.dart
rename to lib/presentation/health_calculators_and_converts/widgets/ibw.dart
index 0e3f725..5f67511 100644
--- a/lib/presentation/health_calculators/widgets/ibw.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart
@@ -8,7 +8,7 @@ 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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class IdealBodyWeightWidget extends StatefulWidget {
Function(dynamic result)? onChange;
diff --git a/lib/presentation/health_calculators/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart
similarity index 91%
rename from lib/presentation/health_calculators/widgets/ovulation.dart
rename to lib/presentation/health_calculators_and_converts/widgets/ovulation.dart
index 476bf72..de209cb 100644
--- a/lib/presentation/health_calculators/widgets/ovulation.dart
+++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart
@@ -8,7 +8,7 @@ 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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
class OvulationWidget extends StatefulWidget {
Function(dynamic result)? onChange;
@@ -78,7 +78,7 @@ class _OvulationWidgetState extends State {
onChange: (val) {
if (val == null) return;
setState(() {
- _ageController.text = selectedDate = Utils.formatDateToDisplay(val!);
+ _ageController.text = selectedDate = Utils.formatDateToDisplay(val);
});
_onInputChanged();
},
@@ -109,7 +109,7 @@ class _OvulationWidgetState extends State {
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isCollapsed: true,
- hintText: '15',
+ hintText: '28',
hintStyle: TextStyle(color: Colors.grey),
),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
@@ -146,7 +146,7 @@ class _OvulationWidgetState extends State {
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isCollapsed: true,
- hintText: '10',
+ hintText: '14',
hintStyle: TextStyle(color: Colors.grey),
),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
@@ -157,17 +157,6 @@ class _OvulationWidgetState extends State {
),
],
).paddingSymmetrical(0.w, 16.w),
- if (provider.ovulationResult != null) ...[
- Divider(height: 1, color: Color(0xFFEEEEEE)),
- // show result UI same as before; keeping unchanged design
- Row(
- children: [
- Expanded(child: Text('Last Period: ${provider.ovulationResult!['lastPeriodDate'] ?? ''}')),
- Expanded(child: Text('Ovulation: ${provider.ovulationResult!['ovulationDate'] ?? ''}')),
- ],
- ).paddingSymmetrical(0.w, 16.w),
- ],
- Divider(height: 1, color: Color(0xFFEEEEEE)),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart
new file mode 100644
index 0000000..17ad1f9
--- /dev/null
+++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart
@@ -0,0 +1,194 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+
+class TriglyceridesWidget extends StatefulWidget {
+ final Function(dynamic result)? onChange;
+
+ const TriglyceridesWidget({super.key, this.onChange});
+
+ @override
+ State createState() => _TriglyceridesWidgetState();
+}
+
+class _TriglyceridesWidgetState extends State {
+ final TextEditingController _mgdlController = TextEditingController();
+ final TextEditingController _mmolController = TextEditingController();
+ final FocusNode _mgdlFocus = FocusNode();
+ final FocusNode _mmolFocus = FocusNode();
+ bool _isProgrammaticChange = false;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Provider.of(
+ context,
+ listen: false,
+ ).clearTriglycerides();
+ });
+ }
+
+ @override
+ void dispose() {
+ _mgdlController.dispose();
+ _mmolController.dispose();
+ _mgdlFocus.dispose();
+ _mmolFocus.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (context, provider, _) {
+ _isProgrammaticChange = true;
+
+ final mgdlText = provider.triMgdlValue ?? '';
+ final mmolText = provider.triMmolValue ?? '';
+
+ if (_mgdlController.text != mgdlText) {
+ _mgdlController.text = mgdlText;
+ _mgdlController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mgdlController.text.length),
+ );
+ }
+
+ if (_mmolController.text != mmolText) {
+ _mmolController.text = mmolText;
+ _mmolController.selection = TextSelection.fromPosition(
+ TextPosition(offset: _mmolController.text.length),
+ );
+ }
+
+ _isProgrammaticChange = false;
+
+ return Container(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.r,
+ hasShadow: true,
+ ),
+ child: Column(
+ children: [
+ _buildInputField(
+ label: "MG/DL",
+ hint: "150",
+ controller: _mgdlController,
+ focusNode: _mgdlFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+ provider.onTriglyceridesChanged(value, 'mg/dL');
+ },
+ ).paddingOnly(top: 16.h),
+
+ Row(
+ children: [
+ const Expanded(
+ flex: 3,
+ child: Divider(height: 1, color: Color(0xFFEEEEEE)),
+ ),
+ SizedBox(width: 8.w),
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.switchBtn,
+ width: 40.h,
+ height: 40.h,
+ ).onPress(() {
+ provider.switchTriglyceridesValues();
+
+ if (provider.triActiveUnit == 'mg/dL') {
+ _mgdlFocus.requestFocus();
+ } else {
+ _mmolFocus.requestFocus();
+ }
+ }),
+ ],
+ ),
+
+ _buildInputField(
+ label: "MMOL/L",
+ hint: "1.7",
+ controller: _mmolController,
+ focusNode: _mmolFocus,
+ onChanged: (value) {
+ if (_isProgrammaticChange) return;
+ provider.onTriglyceridesChanged(value, 'mmol/L');
+ },
+ ).paddingOnly(bottom: 16.h),
+
+ const Divider(height: 1, color: Color(0xFFEEEEEE)),
+
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: AppAssets.globe,
+ width: 18.w,
+ height: 18.w,
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child:
+ "Convert triglyceride values between mg/dL and mmol/L."
+ .toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.inputLabelTextColor,
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildInputField({
+ required String label,
+ required String hint,
+ required TextEditingController controller,
+ required FocusNode focusNode,
+ required ValueChanged onChanged,
+ }) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ label.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.inputLabelTextColor,
+ ),
+ SizedBox(
+ height: 40.h,
+ child: TextField(
+ controller: controller,
+ focusNode: focusNode,
+
+ keyboardType:
+ const TextInputType.numberWithOptions(decimal: true),
+ onChanged: onChanged,
+ cursorHeight: 35.h,
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: hint,
+ hintStyle: const TextStyle(color: Colors.grey),
+ ),
+ style: TextStyle(
+ fontSize: 32.f,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart
index b321ff3..8b10ab0 100644
--- a/lib/presentation/hmg_services/services_page.dart
+++ b/lib/presentation/hmg_services/services_page.dart
@@ -149,7 +149,7 @@ class ServicesPage extends StatelessWidget {
AppAssets.health_converters_icon,
bgColor: AppColors.whiteColor,
true,
- route: AppRoutes.comprehensiveCheckupPage,
+ route: AppRoutes.healthConvertersPage,
),
HmgServicesComponentModel(
11,
diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart
index 8e452ff..cc3c881 100644
--- a/lib/presentation/home/data/landing_page_data.dart
+++ b/lib/presentation/home/data/landing_page_data.dart
@@ -36,7 +36,7 @@ class LandingPageData {
isBold: false,
),
ServiceCardData(
- serviceName: "health_calculators",
+ serviceName: "health_calculators_and_converts",
icon: AppAssets.health_calculators_icon,
title: "Health",
subtitle: "Calculators",
diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart
index 7cd955b..758914b 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/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_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/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.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 'alphabeticScroll.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: CollapsingToolbar(
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: [
Row(
children: [
Expanded(
child: 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(() {});
},
),
),
],
),
// Place Clinic/Hospital buttons under the tabs, styled like prescriptions
if (activeIndex == 0)
Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
model.setIsSortByClinic(true);
},
backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
model.setIsSortByClinic(false);
},
backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
),
SizedBox(height: 8.h),
selectedFilterText!.isNotEmpty
? CustomChipWidget(
chipText: selectedFilterText!,
chipType: ChipTypeEnum.alert,
isSelected: true,
)
: SizedBox(),
activeIndex == 0
? // By Visit - show grouped view when available
model.isLabOrdersLoading
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: 5,
itemBuilder: (context, index) => LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
),
)
: (model.patientLabOrdersViewList.isNotEmpty
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.patientLabOrdersViewList.length,
itemBuilder: (context, index) {
final group = model.patientLabOrdersViewList[index];
final displayName = model.isSortByClinic
? (group.first.clinicDescription ?? 'Unknown')
: (group.first.projectName ?? 'Unknown');
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 400),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () {
// Navigate using the first order of the group
final order = group.first;
model.currentlySelectedPatientOrder = order;
labProvider.getPatientLabResultByHospital(order);
labProvider.getPatientSpecialResult(order);
Navigator.push(
context,
CustomPageRoute(
page: LabResultByClinic(labOrder: order),
),
);
},
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.h),
boxShadow: [
BoxShadow(
color: AppColors.blackColor.withValues(alpha: 0.03),
blurRadius: 6,
offset: Offset(0, 2),
)
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 6.h),
Text(
"${group.length} ${'orders'.needTranslation}",
style: TextStyle(fontSize: 12.h, color: AppColors.greyColor),
),
],
),
),
Icon(Icons.chevron_right, color: AppColors.greyColor),
],
),
),
),
),
),
);
},
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation))
: // By Test or other tabs keep existing behavior
(model.isLabOrdersLoading)
? Column(
children: List.generate(
5,
(index) => LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)),
)
: AlphabeticScroll(
alpahbetsAvailable: model.indexedCharacterForUniqueTest,
details: model.uniqueTestsList,
labViewModel: model,
rangeViewModel: rangeViewModel,
appState: _appState,
)
],
);
},
),
),
));
}
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/extensions/widget_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_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/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.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 'alphabeticScroll.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;
// Scroll controller to control page scrolling when a group expands
late ScrollController _scrollController;
// Map of keys for each group to allow scrolling to them
final Map _groupKeys = {};
@override
void initState() {
_scrollController = ScrollController();
scheduleMicrotask(() {
labProvider.initLabProvider();
});
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
labProvider = Provider.of(context, listen: false);
rangeViewModel = Provider.of(context);
_appState = getIt();
return CollapsingToolbar(
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(
controller: _scrollController,
padding: EdgeInsets.all(24.h),
physics: AlwaysScrollableScrollPhysics(),
child: Consumer(
builder: (context, model, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: 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(() {});
},
),
),
],
),
// Place Clinic/Hospital buttons under the tabs, styled like prescriptions
if (activeIndex == 0)
Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
model.setIsSortByClinic(true);
},
backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
model.setIsSortByClinic(false);
},
backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
),
SizedBox(height: 8.h),
selectedFilterText!.isNotEmpty
? CustomChipWidget(
chipText: selectedFilterText!,
chipType: ChipTypeEnum.alert,
isSelected: true,
)
: SizedBox(),
activeIndex == 0
? // By Visit - show grouped view when available
model.isLabOrdersLoading
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: 5,
itemBuilder: (context, index) => LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
),
)
: (model.patientLabOrdersViewList.isNotEmpty
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: model.patientLabOrdersViewList.length,
itemBuilder: (context, index) {
final group = model.patientLabOrdersViewList[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 400),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: AnimatedContainer(
key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: InkWell(
onTap: () {
setState(() {
expandedIndex = expandedIndex == index ? null : index;
});
// After rebuild, ensure the expanded group is visible
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _groupKeys[index];
if (key != null && key.currentContext != null && expandedIndex == index) {
Scrollable.ensureVisible(
key.currentContext!,
duration: Duration(milliseconds: 350),
curve: Curves.easeInOut,
alignment: 0.1,
);
}
});
},
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomButton(
text: "${group.length} ${'results'.needTranslation}",
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 30.h,
),
Icon(expandedIndex == index ? Icons.expand_less : Icons.expand_more, ),
],
),
SizedBox(height: 8.h),
Text(
model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'),
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
// Expanded list of orders when group is expanded
AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: expandedIndex == index
? Container(
key: ValueKey(index),
padding: EdgeInsets.only(top: 12.h),
child: Column(
children: group.map((order) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 24.h,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500),
SizedBox(height: 6.h),
],
),
),
],
),
SizedBox(height: 8.h),
Row(
children: [
CustomButton(
text: ("Order No: ".needTranslation + order.orderNo!),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
),
SizedBox(width: 8.h),
CustomButton(
text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
),
SizedBox(width: 8.h),
],
),
SizedBox(height: 8.h),
Row(
children: [
CustomButton(
text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""),
onPressed: () {},
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 10,
fontWeight: FontWeight.w500,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 24.h,
),
// SizedBox(height: 8.h),
// Expanded(
// flex: 6,
// child: CustomButton(
// text: LocaleKeys.view.tr(context: context),
// onPressed: () async {
// model.currentlySelectedPatientOrder = order;
// labProvider.getPatientLabResultByHospital(order);
// labProvider.getPatientSpecialResult(order);
// Navigator.push(
// context,
// CustomPageRoute(
// page: LabResultByClinic(labOrder: order),
// ),
// );
// },
// backgroundColor: AppColors.bgRedLightColor,
// borderColor: AppColors.primaryRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: 14,
// fontWeight: FontWeight.w500,
// borderRadius: 12,
// padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
// height: 40.h,
// ),
// ),
],
),
Row(
children: [
Expanded(flex:6, child: SizedBox()),
Expanded(
flex: 1,
child: Container(
height: 40.h,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 12,
),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Transform.flip(
flipX: _appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.whiteColor,
fit: BoxFit.contain,
),
),
),
).onPress(() {
model.currentlySelectedPatientOrder = order;
labProvider.getPatientLabResultByHospital(order);
labProvider.getPatientSpecialResult(order);
Navigator.of(context).push(
CustomPageRoute(page: LabResultByClinic(labOrder: order)),
);
}),
)
],
),
])),
);
}).toList(),
),
)
: SizedBox.shrink(),
),
],
),
),
),
),
),
),
);
},
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation))
: // By Test or other tabs keep existing behavior
(model.isLabOrdersLoading)
? Column(
children: List.generate(
5,
(index) => LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
)),
)
: AlphabeticScroll(
alpahbetsAvailable: model.indexedCharacterForUniqueTest,
details: model.uniqueTestsList,
labViewModel: model,
rangeViewModel: rangeViewModel,
appState: _appState,
)
],
);
},
),
),
);
}
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/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart
index 709e4e3..cbd7b56 100644
--- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart
+++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart
@@ -3,6 +3,10 @@ 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_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/utils/date_util.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';
@@ -12,6 +16,9 @@ import 'package:hmg_patient_app_new/features/medical_file/models/patient_sicklea
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/loader/bottomsheet_loader.dart';
+import 'package:open_filex/open_filex.dart';
import 'package:provider/provider.dart';
import 'widgets/patient_sick_leave_card.dart';
@@ -24,6 +31,7 @@ class PatientSickleavesListPage extends StatefulWidget {
}
class _PatientSickleavesListPageState extends State {
+ int? expandedIndex;
late MedicalFileViewModel medicalFileViewModel;
@override
@@ -37,53 +45,257 @@ class _PatientSickleavesListPageState extends State {
@override
Widget build(BuildContext context) {
+ AppState appState = getIt.get();
medicalFileViewModel = Provider.of(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}",
child: SingleChildScrollView(
- child: Consumer(builder: (context, medicalFileVM, child) {
+ child: Consumer(builder: (context, model, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- SizedBox(height: 24.h),
- ListView.separated(
- padding: EdgeInsets.zero,
- scrollDirection: Axis.vertical,
- itemCount: medicalFileVM.isPatientSickLeaveListLoading
- ? 3
- : medicalFileVM.patientSickLeaveList.isNotEmpty
- ? medicalFileVM.patientSickLeaveList.length
+ SizedBox(height: 16.h),
+ // Clinic & Hospital Sort
+ Row(
+ children: [
+ CustomButton(
+ text: LocaleKeys.byClinic.tr(context: context),
+ onPressed: () {
+ model.setIsSickLeavesSortByClinic(true);
+ },
+ backgroundColor: model.isSickLeavesSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
+ borderColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
+ textColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: LocaleKeys.byHospital.tr(context: context),
+ onPressed: () {
+ model.setIsSickLeavesSortByClinic(false);
+ },
+ backgroundColor: model.isSickLeavesSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
+ borderColor: model.isSickLeavesSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
+ textColor: model.isSickLeavesSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ SizedBox(height: 20.h),
+ // Expandable list
+ ListView.builder(
+ itemCount: model.isPatientSickLeaveListLoading
+ ? 4
+ : model.patientSickLeaveList.isNotEmpty
+ ? model.patientSickLeavesViewList.length
: 1,
- shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ padding: const EdgeInsets.only(left: 0, right: 8),
itemBuilder: (context, index) {
- return medicalFileVM.isPatientSickLeaveListLoading
+ final isExpanded = expandedIndex == index;
+ return model.isPatientSickLeaveListLoading
? PatientSickLeaveCard(
patientSickLeavesResponseModel: PatientSickLeavesResponseModel(),
isLoading: true,
).paddingSymmetrical(24.h, 0.0)
- : medicalFileVM.patientSickLeaveList.isNotEmpty
+ : model.patientSickLeaveList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
- duration: const Duration(milliseconds: 1000),
+ duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
- child: PatientSickLeaveCard(
- patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first,
- isLoading: false,
- isSickLeaveListPage: true,
- ).paddingSymmetrical(24.h, 0.0),
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CustomButton(
+ text: "${model.patientSickLeavesViewList[index].sickLeavesList!.length} ${LocaleKeys.sickSubtitle.tr(context: context)} Available",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 30.h,
+ ),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ model.patientSickLeavesViewList[index].filterName!.toText16(isBold: true)
+ ],
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...model.patientSickLeavesViewList[index].sickLeavesList!.map((sickLeave) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Image.network(
+ sickLeave.doctorImageURL!,
+ width: 24.h,
+ height: 24.h,
+ fit: BoxFit.fill,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ CustomButton(
+ text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!,
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: "${sickLeave.sickLeaveDays} Days",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Expanded(
+ flex: 6,
+ child: CustomButton(
+ text: "Download Report".needTranslation,
+ onPressed: () async {
+ LoaderBottomSheet.showLoader();
+ await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async {
+ LoaderBottomSheet.hideLoader();
+ if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) {
+ String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf");
+ try {
+ OpenFilex.open(path);
+ } catch (ex) {
+ debugPrint("Error opening file: $ex");
+ }
+ }
+ });
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ icon: AppAssets.download,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 14.h,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
+ ],
+ );
+ }),
+ ],
+ ),
+ )
+ : SizedBox.shrink(),
+ ),
+ ],
+ ),
+ ),
+ ),
),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any sick leaves yet.".needTranslation);
},
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h),
- ),
- SizedBox(height: 60.h),
+ ).paddingSymmetrical(24.h, 0.h),
],
);
}),
diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart
index 4047a53..b15266c 100644
--- a/lib/presentation/medical_report/medical_reports_page.dart
+++ b/lib/presentation/medical_report/medical_reports_page.dart
@@ -2,6 +2,9 @@ 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_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/utils/date_util.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';
@@ -17,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:open_filex/open_filex.dart';
import 'package:provider/provider.dart';
class MedicalReportsPage extends StatefulWidget {
@@ -27,10 +31,12 @@ class MedicalReportsPage extends StatefulWidget {
}
class _MedicalReportsPageState extends State {
+ int? expandedIndex;
late MedicalFileViewModel medicalFileViewModel;
@override
Widget build(BuildContext context) {
+ AppState appState = getIt.get();
medicalFileViewModel = Provider.of(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
@@ -44,16 +50,53 @@ class _MedicalReportsPageState extends State {
return Column(
children: [
SizedBox(height: 16.h),
+ // Status filter tabs
+ Row(
+ children: [
+ CustomButton(
+ text: LocaleKeys.byClinic.tr(context: context),
+ onPressed: () {
+ medicalFileVM.setIsMedicalReportsSortByClinic(true);
+ },
+ backgroundColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
+ borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
+ textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: LocaleKeys.byHospital.tr(context: context),
+ onPressed: () {
+ medicalFileVM.setIsMedicalReportsSortByClinic(false);
+ },
+ backgroundColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
+ borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor,
+ textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ borderRadius: 10,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ],
+ ).paddingSymmetrical(24.h, 0.h),
+ SizedBox(height: 20.h),
Row(
children: [
CustomButton(
text: "Requested".needTranslation,
onPressed: () {
+ setState(() {
+ expandedIndex = null;
+ });
medicalFileViewModel.onMedicalReportTabChange(0);
},
backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.bgRedLightColor : AppColors.whiteColor,
- borderColor:
- medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
+ borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -65,11 +108,13 @@ class _MedicalReportsPageState extends State {
CustomButton(
text: LocaleKeys.ready.tr(context: context),
onPressed: () {
+ setState(() {
+ expandedIndex = null;
+ });
medicalFileViewModel.onMedicalReportTabChange(1);
},
backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.bgRedLightColor : AppColors.whiteColor,
- borderColor:
- medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
+ borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -81,11 +126,13 @@ class _MedicalReportsPageState extends State {
CustomButton(
text: LocaleKeys.cancelled.tr(context: context),
onPressed: () {
+ setState(() {
+ expandedIndex = null;
+ });
medicalFileViewModel.onMedicalReportTabChange(2);
},
backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.bgRedLightColor : AppColors.whiteColor,
- borderColor:
- medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
+ borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
@@ -95,34 +142,27 @@ class _MedicalReportsPageState extends State {
),
],
).paddingSymmetrical(24.h, 0.h),
- // CustomTabBar(
- // activeTextColor: Color(0xffED1C2B),
- // activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
- // tabs: [
- // CustomTabBarModel(null, "Requested".needTranslation),
- // CustomTabBarModel(null, "Ready".needTranslation),
- // CustomTabBarModel(null, "Cancelled".needTranslation),
- // ],
- // onTabChange: (index) {
- // medicalFileViewModel.onMedicalReportTabChange(index);
- // },
- // ).paddingSymmetrical(24.h, 0.h),
- ListView.separated(
- padding: EdgeInsets.only(top: 24.h),
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
+ SizedBox(height: 12.h),
+ // Clinic & Hospital Sort
+
+ // Expandable list
+ ListView.builder(
itemCount: medicalFileViewModel.isPatientMedicalReportsListLoading
- ? 3
+ ? 4
: medicalFileViewModel.patientMedicalReportList.isNotEmpty
- ? medicalFileViewModel.patientMedicalReportList.length
+ ? medicalFileViewModel.patientMedicalReportsViewList.length
: 1,
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ padding: const EdgeInsets.only(left: 0, right: 8),
itemBuilder: (context, index) {
+ final isExpanded = expandedIndex == index;
return medicalFileViewModel.isPatientMedicalReportsListLoading
? PatientMedicalReportCard(
patientMedicalReportResponseModel: PatientMedicalReportResponseModel(),
medicalFileViewModel: medicalFileVM,
isLoading: true,
- ).paddingSymmetrical(24.h, 0.h)
+ ).paddingSymmetrical(24.h, 8.h)
: medicalFileViewModel.patientMedicalReportList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
@@ -133,22 +173,185 @@ class _MedicalReportsPageState extends State {
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
- decoration: RoundedRectangleBorder()
- .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
- child: PatientMedicalReportCard(
- patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index],
- medicalFileViewModel: medicalFileVM,
- isLoading: false,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CustomButton(
+ text: "${medicalFileVM.patientMedicalReportsViewList[index].medicalReportsList!.length} Reports Available",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 30.h,
+ ),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ medicalFileVM.patientMedicalReportsViewList[index].filterName!.toText16(isBold: true)
+ ],
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...medicalFileVM.patientMedicalReportsViewList[index].medicalReportsList!.map((report) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Image.network(
+ report.doctorImageURL!,
+ width: 24.h,
+ height: 24.h,
+ fit: BoxFit.fill,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(child: report.doctorName!.toText14(weight: FontWeight.w500)),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ CustomButton(
+ text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(report.appointmentDate), false),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: medicalFileVM.isMedicalReportsSortByClinic ? report.projectName! : report.clinicDescription!,
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: report.statusDesc!,
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ if (medicalFileVM.selectedMedicalReportsTabIndex == 1)
+ Expanded(
+ flex: 6,
+ child: CustomButton(
+ text: "Download Report".needTranslation,
+ onPressed: () async {
+ LoaderBottomSheet.showLoader();
+ await medicalFileViewModel.getPatientMedicalReportPDF(report, appState.getAuthenticatedUser()!).then((val) async {
+ LoaderBottomSheet.hideLoader();
+ if (medicalFileViewModel.patientMedicalReportPDFBase64.isNotEmpty) {
+ String path = await Utils.createFileFromString(medicalFileViewModel.patientMedicalReportPDFBase64, "pdf");
+ try {
+ OpenFilex.open(path);
+ } catch (ex) {
+ debugPrint("Error opening file: $ex");
+ }
+ }
+ });
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ icon: AppAssets.download,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 14.h,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
+ ],
+ );
+ }),
+ ],
+ ),
+ )
+ : SizedBox.shrink(),
+ ),
+ ],
+ ),
),
- ).paddingSymmetrical(24.h, 0.h),
+ ),
),
),
)
: Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation)
.paddingSymmetrical(24.h, 24.h);
},
- separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
- ),
+ ).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 24.h),
],
);
diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart
index 9c22a1f..c0eb782 100644
--- a/lib/presentation/radiology/radiology_orders_page.dart
+++ b/lib/presentation/radiology/radiology_orders_page.dart
@@ -3,9 +3,11 @@ 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/utils/date_util.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/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@@ -33,15 +35,25 @@ class _RadiologyOrdersPageState extends State {
late RadiologyViewModel radiologyViewModel;
String selectedFilterText = '';
int? expandedIndex;
+ // Scroll controller to ensure expanded group is visible
+ late ScrollController _scrollController;
+ final Map _groupKeys = {};
@override
void initState() {
+ _scrollController = ScrollController();
scheduleMicrotask(() {
radiologyViewModel.initRadiologyViewModel();
});
super.initState();
}
+ @override
+ void dispose() {
+ _scrollController.dispose();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
radiologyViewModel = Provider.of(context);
@@ -68,6 +80,8 @@ class _RadiologyOrdersPageState extends State {
}
},
child: SingleChildScrollView(
+ controller: _scrollController,
+ physics: NeverScrollableScrollPhysics(),
child: Consumer(
builder: (context, model, child) {
// Build grouping lists if we have data and none constructed yet
@@ -185,13 +199,31 @@ class _RadiologyOrdersPageState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ // Group header container with key so we can scroll to it
GestureDetector(
onTap: () {
setState(() {
expandedIndex = expandedIndex == index ? null : index;
});
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
+ if (key.currentContext != null && expandedIndex == index) {
+ // Delay scrolling to wait for expansion animation
+ Future.delayed(Duration(milliseconds: 450), () {
+ if (key.currentContext != null) {
+ Scrollable.ensureVisible(
+ key.currentContext!,
+ duration: Duration(milliseconds: 350),
+ curve: Curves.easeInOut,
+ alignment: 0.0,
+ );
+ }
+ });
+ }
+ });
},
child: Container(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
@@ -212,80 +244,175 @@ class _RadiologyOrdersPageState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ CustomButton(
+ text: "${group.length} ${'results'.needTranslation}",
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 30.h,
+ ),
+ Icon(expandedIndex == index ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
Text(
displayName,
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
- SizedBox(height: 6.h),
- Text(
- "${group.length} ${'orders'.needTranslation}",
- style: TextStyle(fontSize: 12.h, color: AppColors.greyColor),
- ),
],
),
),
- Icon(expandedIndex == index ? Icons.expand_less : Icons.chevron_right, color: AppColors.greyColor),
],
),
),
),
- AnimatedCrossFade(
- firstChild: SizedBox.shrink(),
- secondChild: Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 6.h),
- child: Column(
- children: group.map((order) {
- return Container(
- margin: EdgeInsets.only(bottom: 8.h),
- padding: EdgeInsets.all(12.h),
- decoration: BoxDecoration(
- color: AppColors.whiteColor,
- borderRadius: BorderRadius.circular(8.h),
- border: Border.all(color: AppColors.borderOnlyColor.withValues(alpha: 0.05)),
- ),
- child: Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(order.doctorName ?? '', style: TextStyle(fontSize: 14.h, fontWeight: FontWeight.w600)),
- SizedBox(height: 6.h),
- Text(order.description ?? '', style: TextStyle(fontSize: 13.h, color: AppColors.textColorLight)),
- ],
+ AnimatedSwitcher(
+ duration: Duration(milliseconds: 400),
+ child: expandedIndex == index
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.only(top: 12.h),
+ child: Column(
+ children: group.map((order) {
+ return Container(
+ margin: EdgeInsets.only(bottom: 12.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 12.h,
+ hasShadow: true,
),
- ),
- SizedBox(width: 8.h),
- CustomButton(
- icon: AppAssets.view_report_icon,
- iconColor: AppColors.primaryRedColor,
- iconSize: 16.h,
- text: LocaleKeys.viewReport.tr(context: context),
- onPressed: () {
- model.navigationService.push(
- CustomPageRoute(
- page: RadiologyResultPage(patientRadiologyResponseModel: order),
- ),
- );
- },
- backgroundColor: AppColors.secondaryLightRedColor,
- borderColor: AppColors.secondaryLightRedColor,
- textColor: AppColors.primaryRedColor,
- fontSize: 14,
- fontWeight: FontWeight.bold,
- borderRadius: 12,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 36.h,
- ),
- ],
+ child: Padding(
+ padding: EdgeInsets.all(14.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Image.network(
+ order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
+ width: 24.h,
+ height: 24.h,
+ fit: BoxFit.cover,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+
+ children: [
+ (order.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
+ SizedBox(height: 6.h),
+
+
+ ],
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(children: [
+ CustomButton(
+ text: order.description!,
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ )
+
+ ]
+ ),
+ SizedBox(height: 6.h),
+
+ Row(
+ children: [
+ CustomButton(
+ text: DateUtil.formatDateToDate(order.orderDate ?? order.appointmentDate ?? "", false),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ SizedBox(width: 8.h),
+ CustomButton(
+ text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""),
+ onPressed: () {},
+ backgroundColor: AppColors.greyColor,
+ borderColor: AppColors.greyColor,
+ textColor: AppColors.blackColor,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ borderRadius: 8,
+ padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 24.h,
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ children: [
+ Expanded(
+ flex: 6,
+ child: SizedBox(),
+ ),
+ SizedBox(width: 8.h),
+ Expanded(
+ flex: 1,
+ child: Container(
+ height: 40.h,
+ width: 40.w,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.textColor,
+ borderRadius: 12,
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(12.h),
+ child: Transform.flip(
+ flipX: false,
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.forward_arrow_icon_small,
+ iconColor: AppColors.whiteColor,
+ fit: BoxFit.contain,
+ ),
+ ),
+ ),
+ ).onPress(() {
+ model.navigationService.push(
+ CustomPageRoute(
+ page: RadiologyResultPage(patientRadiologyResponseModel: order),
+ ),
+ );
+ }),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }).toList(),
),
- );
- }).toList(),
- ),
- ),
- crossFadeState: expandedIndex == index ? CrossFadeState.showSecond : CrossFadeState.showFirst,
- duration: Duration(milliseconds: 250),
+ )
+ : SizedBox.shrink(),
),
],
),
diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart
index d25a9de..f335f34 100644
--- a/lib/routes/app_routes.dart
+++ b/lib/routes/app_routes.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
@@ -6,7 +7,7 @@ import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_p
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
-import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculators_page.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart';
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';
@@ -33,6 +34,7 @@ class AppRoutes {
static const String eReferralPage = '/erReferralPage';
static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage';
static const String healthCalculatorsPage = '/healthCalculatorsPage';
+ static const String healthConvertersPage = '/healthConvertersPage';
static const String homeHealthCarePage = '/homeHealthCarePage';
static const String zoomCallPage = '/zoomCallPage';
static const String bloodDonationPage = '/bloodDonationPage';
@@ -79,7 +81,7 @@ class AppRoutes {
huaweiHealthExample: (context) => HuaweiHealthExample(),
//
- healthCalculatorsPage: (context) => HealthCalculatorsPage()
-
+ healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator),
+ healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter)
};
}
diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart
index b1b438b..ca40c32 100644
--- a/lib/widgets/appbar/collapsing_list_view.dart
+++ b/lib/widgets/appbar/collapsing_list_view.dart
@@ -71,52 +71,52 @@ class CollapsingListView extends StatelessWidget {
backgroundColor: AppColors.bgScaffoldColor,
bottom: isLeading
? ScrollAnimatedTitle(
- title: title,
- showBack: true,
- controller: _controller,
- search: search,
- report: report,
- logout: logout,
- history: history,
- instructions: instructions,
- requests: requests,
- bottomChild: bottomChild,
- trailing: trailing,
- )
+ title: title,
+ showBack: true,
+ controller: _controller,
+ search: search,
+ report: report,
+ logout: logout,
+ history: history,
+ instructions: instructions,
+ requests: requests,
+ bottomChild: bottomChild,
+ trailing: trailing,
+ )
: null,
leading: isLeading
? Transform.flip(
- flipX: appState.isArabic(),
- child: IconButton(
- icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 24.h, height: 24.h),
- padding: EdgeInsets.only(left: 12),
- onPressed: () {
- if (leadingCallback != null) {
- leadingCallback!();
- } else {
- context.pop();
- }
- },
- highlightColor: Colors.transparent,
- ),
- )
+ flipX: appState.isArabic(),
+ child: IconButton(
+ icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h),
+ padding: EdgeInsets.only(left: 12),
+ onPressed: () {
+ if (leadingCallback != null) {
+ leadingCallback!();
+ } else {
+ context.pop();
+ }
+ },
+ highlightColor: Colors.transparent,
+ ),
+ )
: ScrollAnimatedTitle(
- title: title,
- showBack: false,
- controller: _controller,
- search: search,
- report: report,
- logout: logout,
- history: history,
- instructions: instructions,
- requests: requests,
- bottomChild: bottomChild,
- trailing: trailing,
- ),
+ title: title,
+ showBack: false,
+ controller: _controller,
+ search: search,
+ report: report,
+ logout: logout,
+ history: history,
+ instructions: instructions,
+ requests: requests,
+ bottomChild: bottomChild,
+ trailing: trailing,
+ ),
),
SliverList(
delegate: SliverChildBuilderDelegate(
- (context, index) => child,
+ (context, index) => child,
childCount: 1,
),
),