Health calculators & converters

pull/133/head
aamir-csol 2 weeks ago
parent 54f8391066
commit 3601e39a29

@ -64,6 +64,11 @@ class BookAppointmentsViewModel extends ChangeNotifier {
List<DoctorsListResponseModel> doctorsList = []; List<DoctorsListResponseModel> doctorsList = [];
List<DoctorsListResponseModel> filteredDoctorList = []; List<DoctorsListResponseModel> filteredDoctorList = [];
// Grouped doctors lists
List<List<DoctorsListResponseModel>> doctorsListByClinic = [];
List<List<DoctorsListResponseModel>> doctorsListByHospital = [];
List<List<DoctorsListResponseModel>> doctorsListGrouped = [];
List<DoctorsListResponseModel> liveCareDoctorsList = []; List<DoctorsListResponseModel> liveCareDoctorsList = [];
List<PatientDentalPlanEstimationResponseModel> patientDentalPlanEstimationList = []; List<PatientDentalPlanEstimationResponseModel> patientDentalPlanEstimationList = [];
@ -149,14 +154,28 @@ class BookAppointmentsViewModel extends ChangeNotifier {
setIsSortByClinic(bool value) { setIsSortByClinic(bool value) {
isSortByClinic = value; isSortByClinic = value;
if (isSortByClinic) { doctorsListGrouped = isSortByClinic ? doctorsListByClinic : doctorsListByHospital;
doctorsList.sort((a, b) => a.clinicName!.compareTo(b.clinicName!));
} else {
doctorsList.sort((a, b) => a.projectName!.compareTo(b.projectName!));
}
notifyListeners(); notifyListeners();
} }
// Group doctors by clinic and hospital
void _groupDoctorsList() {
final clinicMap = <String, List<DoctorsListResponseModel>>{};
final hospitalMap = <String, List<DoctorsListResponseModel>>{};
for (var doctor in doctorsList) {
final clinicKey = (doctor.clinicName ?? 'Unknown').trim();
clinicMap.putIfAbsent(clinicKey, () => []).add(doctor);
final hospitalKey = (doctor.projectName ?? 'Unknown').trim();
hospitalMap.putIfAbsent(hospitalKey, () => []).add(doctor);
}
doctorsListByClinic = clinicMap.values.toList();
doctorsListByHospital = hospitalMap.values.toList();
doctorsListGrouped = isSortByClinic ? doctorsListByClinic : doctorsListByHospital;
}
BookAppointmentsViewModel( BookAppointmentsViewModel(
{required this.bookAppointmentsRepo, {required this.bookAppointmentsRepo,
required this.errorHandlerService, required this.errorHandlerService,
@ -404,6 +423,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
initializeFilteredList(); initializeFilteredList();
clearSearchFilters(); clearSearchFilters();
getFiltersFromDoctorList(); getFiltersFromDoctorList();
_groupDoctorsList();
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);
@ -435,6 +455,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
initializeFilteredList(); initializeFilteredList();
clearSearchFilters(); clearSearchFilters();
getFiltersFromDoctorList(); getFiltersFromDoctorList();
_groupDoctorsList();
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);
@ -1102,6 +1123,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
// initializeFilteredList(); // initializeFilteredList();
// clearSearchFilters(); // clearSearchFilters();
// getFiltersFromDoctorList(); // getFiltersFromDoctorList();
_groupDoctorsList();
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -40,8 +40,14 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
late AppState appState; late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel;
// Scroll controller to control page scrolling when a group expands
late ScrollController _scrollController;
// Map of keys for each item to allow scrolling to them
final Map<int, GlobalKey> _itemKeys = {};
@override @override
void initState() { void initState() {
_scrollController = ScrollController();
scheduleMicrotask(() { scheduleMicrotask(() {
if (bookAppointmentsViewModel.isLiveCareSchedule) { if (bookAppointmentsViewModel.isLiveCareSchedule) {
bookAppointmentsViewModel.getLiveCareDoctorsList(); bookAppointmentsViewModel.getLiveCareDoctorsList();
@ -58,6 +64,12 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
super.initState(); super.initState();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false); bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
@ -67,6 +79,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
body: CollapsingListView( body: CollapsingListView(
title: "Choose Doctor".needTranslation, title: "Choose Doctor".needTranslation,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _scrollController,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.h), padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) { child: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
@ -149,9 +162,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isDoctorsListLoading itemCount: bookAppointmentsVM.isDoctorsListLoading
? 5 ? 5
: (bookAppointmentsVM.isLiveCareSchedule : (bookAppointmentsVM.doctorsListGrouped.isNotEmpty ? bookAppointmentsVM.doctorsListGrouped.length : 1),
? (bookAppointmentsVM.liveCareDoctorsList.isNotEmpty ? bookAppointmentsVM.liveCareDoctorsList.length : 1)
: (bookAppointmentsVM.doctorsList.isNotEmpty ? bookAppointmentsVM.doctorsList.length : 1)),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isExpanded = expandedIndex == index; final isExpanded = expandedIndex == index;
return bookAppointmentsVM.isDoctorsListLoading return bookAppointmentsVM.isDoctorsListLoading
@ -160,7 +171,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
isLoading: true, isLoading: true,
bookAppointmentsViewModel: bookAppointmentsViewModel, bookAppointmentsViewModel: bookAppointmentsViewModel,
) )
: checkIsDoctorsListEmpty() : bookAppointmentsVM.doctorsListGrouped.isEmpty
? Utils.getNoDataWidget(context, noDataText: "No Doctor found for selected criteria...".needTranslation) ? Utils.getNoDataWidget(context, noDataText: "No Doctor found for selected criteria...".needTranslation)
: AnimationConfiguration.staggeredList( : AnimationConfiguration.staggeredList(
position: index, position: index,
@ -169,6 +180,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
verticalOffset: 100.0, verticalOffset: 100.0,
child: FadeInAnimation( child: FadeInAnimation(
child: AnimatedContainer( child: AnimatedContainer(
key: _itemKeys.putIfAbsent(index, () => GlobalKey()),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
@ -177,15 +189,69 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
setState(() { setState(() {
expandedIndex = isExpanded ? null : index; expandedIndex = isExpanded ? null : index;
}); });
// After rebuild, ensure the expanded item is visible
WidgetsBinding.instance.addPostFrameCallback((_) {
final key = _itemKeys[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: [
// Header row with count badge and expand/collapse icon
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomButton(
text: "${bookAppointmentsVM.doctorsListGrouped[index].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.expand_more),
],
),
SizedBox(height: 8.h),
// Clinic/Hospital name as group title
Text(
bookAppointmentsVM.isSortByClinic
? (bookAppointmentsVM.doctorsListGrouped[index].first.clinicName ?? 'Unknown')
: (bookAppointmentsVM.doctorsListGrouped[index].first.projectName ?? 'Unknown'),
style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis,
),
// Expanded content - list of doctors in this group
AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isExpanded
? Container(
key: ValueKey<int>(index),
padding: EdgeInsets.only(top: 12.h),
child: Column(
children: bookAppointmentsVM.doctorsListGrouped[index].map<Widget>((doctor) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
child: DoctorCard( child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index], doctorsListResponseModel: doctor,
isLoading: false, isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel, bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() async { ).onPress(() async {
bookAppointmentsVM bookAppointmentsVM.setSelectedDoctor(doctor);
.setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -205,6 +271,15 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
); );
}); });
}), }),
);
}).toList(),
),
)
: SizedBox.shrink(),
),
],
),
),
), ),
), ),
), ),

@ -49,28 +49,41 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
String? get mmolValue => _mmolValue; String? get mmolValue => _mmolValue;
// Store the original entered value and which field it was entered in
String? _originalValue;
String _originalUnit = 'mg/dL'; // which field the original value was entered in
bool _isSwapped = false; // toggle state for switch
String _activeUnit = 'mg/dL'; // current source unit String _activeUnit = 'mg/dL'; // current source unit
String get activeUnit => _activeUnit; String get activeUnit => _activeUnit;
// ================== BLOOD CHOLESTEROL ================== // ================== BLOOD CHOLESTEROL ==================
String? _cholMgdlValue; String? _cholMgdlValue;
String? _cholMmolValue; String? _cholMmolValue;
String? _cholOriginalValue;
String _cholOriginalUnit = 'mg/dL';
bool _cholIsSwapped = false;
String _cholActiveUnit = 'mg/dL'; String _cholActiveUnit = 'mg/dL';
String? get cholMgdlValue => _cholMgdlValue; String? get cholMgdlValue => _cholMgdlValue;
String? get cholMmolValue => _cholMmolValue; String? get cholMmolValue => _cholMmolValue;
String get cholActiveUnit => _cholActiveUnit; String get cholActiveUnit => _cholActiveUnit;
// ================== TRIGLYCERIDES ================== // ================== TRIGLYCERIDES ==================
String? _triMgdlValue; String? _triMgdlValue;
String? _triMmolValue; String? _triMmolValue;
String? _triOriginalValue;
String _triOriginalUnit = 'mg/dL';
bool _triIsSwapped = false;
String _triActiveUnit = 'mg/dL'; String _triActiveUnit = 'mg/dL';
String? get triMgdlValue => _triMgdlValue; String? get triMgdlValue => _triMgdlValue;
String? get triMmolValue => _triMmolValue; String? get triMmolValue => _triMmolValue;
String get triActiveUnit => _triActiveUnit;
String get triActiveUnit => _triActiveUnit;
// Generic helpers // Generic helpers
@ -726,13 +739,10 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
final DateTime firstTrimesterStart = lmp; final DateTime firstTrimesterStart = lmp;
final DateTime firstTrimesterEnd = lmp.add(const Duration(days: 83)); final DateTime firstTrimesterEnd = lmp.add(const Duration(days: 83));
final DateTime secondTrimesterStart = final DateTime secondTrimesterStart = firstTrimesterEnd.add(const Duration(days: 1));
firstTrimesterEnd.add(const Duration(days: 1)); final DateTime secondTrimesterEnd = lmp.add(const Duration(days: 195));
final DateTime secondTrimesterEnd =
lmp.add(const Duration(days: 195));
final DateTime thirdTrimesterStart = final DateTime thirdTrimesterStart = secondTrimesterEnd.add(const Duration(days: 1));
secondTrimesterEnd.add(const Duration(days: 1));
final DateTime thirdTrimesterEnd = dueDate; final DateTime thirdTrimesterEnd = dueDate;
deliveryResult = { deliveryResult = {
@ -741,7 +751,7 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
// Raw DateTime (useful for logic/UI) // Raw DateTime (useful for logic/UI)
'lmpDateTime': lmp, 'lmpDateTime': lmp,
'dueDateTime': dueDate, 'dueDateTime': dueDate,
'dueDateDay' : _formatDateWithDayName(dueDate), 'dueDateDay': _formatDateWithDayName(dueDate),
// Trimester info // Trimester info
'firstTrimester': { 'firstTrimester': {
'start': _formatDate(firstTrimesterStart), 'start': _formatDate(firstTrimesterStart),
@ -769,7 +779,6 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
// Blood sugar conversions // Blood sugar conversions
void calculateBloodSugar({required String valueText, required String unit}) { void calculateBloodSugar({required String valueText, required String unit}) {
if (valueText.trim().isEmpty) { if (valueText.trim().isEmpty) {
@ -838,8 +847,6 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
'dietType': dietType 'dietType': dietType
}; };
void onBloodSugarChanged(String value, String fromUnit) { void onBloodSugarChanged(String value, String fromUnit) {
_activeUnit = fromUnit; _activeUnit = fromUnit;
@ -855,47 +862,105 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
if (fromUnit == 'mg/dL') { if (fromUnit == 'mg/dL') {
_mgdlValue = value; _mgdlValue = value;
_mmolValue = (parsed / 18.0182).toStringAsFixed(1); _mmolValue = (parsed / 18.0182).toStringAsFixed(3);
} else { } else {
_mmolValue = value; _mmolValue = value;
_mgdlValue = (parsed * 18.0182).toStringAsFixed(0); _mgdlValue = (parsed * 18.0182).toStringAsFixed(3);
} }
notifyListeners(); notifyListeners();
} }
void switchBloodSugarValues() { void onBloodSugarMgdlChanged(String value) {
if (_activeUnit == 'mg/dL') { _mgdlValue = value;
final mmol = double.tryParse(_mmolValue ?? ''); _originalValue = value;
if (mmol == null) return; _originalUnit = 'mg/dL';
_isSwapped = false;
_activeUnit = 'mg/dL';
if (value.isEmpty) {
_mmolValue = null;
_originalValue = null;
notifyListeners();
return;
}
final parsed = double.tryParse(value);
if (parsed == null) return;
// Convert mg/dL to mmol/L
_mmolValue = (parsed / 18.0182).toStringAsFixed(3);
notifyListeners();
}
void onBloodSugarMmolChanged(String value) {
_mmolValue = value;
_originalValue = value;
_originalUnit = 'mmol/L';
_isSwapped = false;
_activeUnit = 'mmol/L'; _activeUnit = 'mmol/L';
// mmol stays as source if (value.isEmpty) {
_mmolValue = mmol.toStringAsFixed(1); _mgdlValue = null;
_mgdlValue = (mmol * 18.0182).toStringAsFixed(0); _originalValue = null;
} else { notifyListeners();
final mgdl = double.tryParse(_mgdlValue ?? ''); return;
if (mgdl == null) return; }
_activeUnit = 'mg/dL'; final parsed = double.tryParse(value);
if (parsed == null) return;
// mg/dL stays as source // Convert mmol/L to mg/dL
_mgdlValue = mgdl.toStringAsFixed(0); _mgdlValue = (parsed * 18.0182).toStringAsFixed(3);
_mmolValue = (mgdl / 18.0182).toStringAsFixed(1); notifyListeners();
}
void switchBloodSugarValues() {
// Toggle between two states using the original entered value
if (_originalValue == null || _originalValue!.isEmpty) return;
final originalParsed = double.tryParse(_originalValue!);
if (originalParsed == null) return;
_isSwapped = !_isSwapped;
if (_originalUnit == 'mg/dL') {
if (_isSwapped) {
// Original was in mg/dL, now treat it as mmol/L
_mmolValue = _originalValue; // Keep original format
_mgdlValue = (originalParsed * 18.0182).toStringAsFixed(3);
} else {
// Back to original: value in mg/dL
_mgdlValue = _originalValue; // Keep original format
_mmolValue = (originalParsed / 18.0182).toStringAsFixed(3);
}
} else {
// Original was in mmol/L
if (_isSwapped) {
// Now treat it as mg/dL
_mgdlValue = _originalValue; // Keep original format
_mmolValue = (originalParsed / 18.0182).toStringAsFixed(3);
} else {
// Back to original: value in mmol/L
_mmolValue = _originalValue; // Keep original format
_mgdlValue = (originalParsed * 18.0182).toStringAsFixed(3);
}
} }
notifyListeners(); notifyListeners();
} }
// --- NEW: Method to clear the values --- // --- NEW: Method to clear the values ---
void clearBloodSugar() { void clearBloodSugar() {
_mgdlValue = null; _mgdlValue = null;
_mmolValue = null; _mmolValue = null;
_originalValue = null;
_originalUnit = 'mg/dL';
_isSwapped = false;
_activeUnit = 'mg/dL';
notifyListeners(); notifyListeners();
} }
void onBloodCholesterolChanged(String value, String fromUnit) { void onBloodCholesterolChanged(String value, String fromUnit) {
_cholActiveUnit = fromUnit; _cholActiveUnit = fromUnit;
@ -911,47 +976,104 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
if (fromUnit == 'mg/dL') { if (fromUnit == 'mg/dL') {
_cholMgdlValue = value; _cholMgdlValue = value;
_cholMmolValue = (parsed / 38.67).toStringAsFixed(2); _cholMmolValue = (parsed / 38.67).toStringAsFixed(3);
} else { } else {
_cholMmolValue = value; _cholMmolValue = value;
_cholMgdlValue = (parsed * 38.67).toStringAsFixed(0); _cholMgdlValue = (parsed * 38.67).toStringAsFixed(3);
} }
notifyListeners(); notifyListeners();
} }
void switchBloodCholesterolValues() { void onCholesterolMgdlChanged(String value) {
if (_cholActiveUnit == 'mg/dL') { _cholMgdlValue = value;
final mmol = double.tryParse(_cholMmolValue ?? ''); _cholOriginalValue = value;
if (mmol == null) return; _cholOriginalUnit = 'mg/dL';
_cholIsSwapped = false;
_cholActiveUnit = 'mg/dL';
_cholActiveUnit = 'mmol/L'; if (value.isEmpty) {
_cholMmolValue = null;
_cholOriginalValue = null;
notifyListeners();
return;
}
_cholMmolValue = mmol.toStringAsFixed(2); final parsed = double.tryParse(value);
_cholMgdlValue = (mmol * 38.67).toStringAsFixed(0); if (parsed == null) return;
} else {
final mgdl = double.tryParse(_cholMgdlValue ?? '');
if (mgdl == null) return;
_cholActiveUnit = 'mg/dL'; // Convert mg/dL to mmol/L
_cholMmolValue = (parsed / 38.67).toStringAsFixed(3);
notifyListeners();
}
_cholMgdlValue = mgdl.toStringAsFixed(0); void onCholesterolMmolChanged(String value) {
_cholMmolValue = (mgdl / 38.67).toStringAsFixed(2); _cholMmolValue = value;
_cholOriginalValue = value;
_cholOriginalUnit = 'mmol/L';
_cholIsSwapped = false;
_cholActiveUnit = 'mmol/L';
if (value.isEmpty) {
_cholMgdlValue = null;
_cholOriginalValue = null;
notifyListeners();
return;
} }
final parsed = double.tryParse(value);
if (parsed == null) return;
// Convert mmol/L to mg/dL
_cholMgdlValue = (parsed * 38.67).toStringAsFixed(3);
notifyListeners(); notifyListeners();
} }
void switchBloodCholesterolValues() {
// Toggle between two states using the original entered value
if (_cholOriginalValue == null || _cholOriginalValue!.isEmpty) return;
final originalParsed = double.tryParse(_cholOriginalValue!);
if (originalParsed == null) return;
_cholIsSwapped = !_cholIsSwapped;
if (_cholOriginalUnit == 'mg/dL') {
if (_cholIsSwapped) {
// Original was in mg/dL, now treat it as mmol/L
_cholMmolValue = _cholOriginalValue; // Keep original format
_cholMgdlValue = (originalParsed * 38.67).toStringAsFixed(3);
} else {
// Back to original: value in mg/dL
_cholMgdlValue = _cholOriginalValue; // Keep original format
_cholMmolValue = (originalParsed / 38.67).toStringAsFixed(3);
}
} else {
// Original was in mmol/L
if (_cholIsSwapped) {
// Now treat it as mg/dL
_cholMgdlValue = _cholOriginalValue; // Keep original format
_cholMmolValue = (originalParsed / 38.67).toStringAsFixed(3);
} else {
// Back to original: value in mmol/L
_cholMmolValue = _cholOriginalValue; // Keep original format
_cholMgdlValue = (originalParsed * 38.67).toStringAsFixed(3);
}
}
notifyListeners();
}
void clearBloodCholesterol() { void clearBloodCholesterol() {
_cholMgdlValue = null; _cholMgdlValue = null;
_cholMmolValue = null; _cholMmolValue = null;
_cholOriginalValue = null;
_cholOriginalUnit = 'mg/dL';
_cholIsSwapped = false;
_cholActiveUnit = 'mg/dL'; _cholActiveUnit = 'mg/dL';
notifyListeners(); notifyListeners();
} }
void onTriglyceridesChanged(String value, String fromUnit) { void onTriglyceridesChanged(String value, String fromUnit) {
_triActiveUnit = fromUnit; _triActiveUnit = fromUnit;
@ -967,30 +1089,89 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
if (fromUnit == 'mg/dL') { if (fromUnit == 'mg/dL') {
_triMgdlValue = value; _triMgdlValue = value;
_triMmolValue = (parsed / 88.57).toStringAsFixed(2); _triMmolValue = (parsed / 88.57).toStringAsFixed(3);
} else { } else {
_triMmolValue = value; _triMmolValue = value;
_triMgdlValue = (parsed * 88.57).toStringAsFixed(0); _triMgdlValue = (parsed * 88.57).toStringAsFixed(3);
} }
notifyListeners(); notifyListeners();
} }
void switchTriglyceridesValues() { void onTriglyceridesMgdlChanged(String value) {
if (_triActiveUnit == 'mg/dL') { _triMgdlValue = value;
final mmol = double.tryParse(_triMmolValue ?? ''); _triOriginalValue = value;
if (mmol == null) return; _triOriginalUnit = 'mg/dL';
_triIsSwapped = false;
_triActiveUnit = 'mg/dL';
if (value.isEmpty) {
_triMmolValue = null;
_triOriginalValue = null;
notifyListeners();
return;
}
final parsed = double.tryParse(value);
if (parsed == null) return;
// Convert mg/dL to mmol/L
_triMmolValue = (parsed / 88.57).toStringAsFixed(3);
notifyListeners();
}
void onTriglyceridesMmolChanged(String value) {
_triMmolValue = value;
_triOriginalValue = value;
_triOriginalUnit = 'mmol/L';
_triIsSwapped = false;
_triActiveUnit = 'mmol/L'; _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'; if (value.isEmpty) {
_triMgdlValue = mgdl.toStringAsFixed(0); _triMgdlValue = null;
_triMmolValue = (mgdl / 88.57).toStringAsFixed(2); _triOriginalValue = null;
notifyListeners();
return;
}
final parsed = double.tryParse(value);
if (parsed == null) return;
// Convert mmol/L to mg/dL
_triMgdlValue = (parsed * 88.57).toStringAsFixed(3);
notifyListeners();
}
void switchTriglyceridesValues() {
// Toggle between two states using the original entered value
if (_triOriginalValue == null || _triOriginalValue!.isEmpty) return;
final originalParsed = double.tryParse(_triOriginalValue!);
if (originalParsed == null) return;
_triIsSwapped = !_triIsSwapped;
if (_triOriginalUnit == 'mg/dL') {
if (_triIsSwapped) {
// Original was in mg/dL, now treat it as mmol/L
_triMmolValue = _triOriginalValue; // Keep original format
_triMgdlValue = (originalParsed * 88.57).toStringAsFixed(3);
} else {
// Back to original: value in mg/dL
_triMgdlValue = _triOriginalValue; // Keep original format
_triMmolValue = (originalParsed / 88.57).toStringAsFixed(3);
}
} else {
// Original was in mmol/L
if (_triIsSwapped) {
// Now treat it as mg/dL
_triMgdlValue = _triOriginalValue; // Keep original format
_triMmolValue = (originalParsed / 88.57).toStringAsFixed(3);
} else {
// Back to original: value in mmol/L
_triMmolValue = _triOriginalValue; // Keep original format
_triMgdlValue = (originalParsed * 88.57).toStringAsFixed(3);
}
} }
notifyListeners(); notifyListeners();
@ -999,10 +1180,10 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
void clearTriglycerides() { void clearTriglycerides() {
_triMgdlValue = null; _triMgdlValue = null;
_triMmolValue = null; _triMmolValue = null;
_triOriginalValue = null;
_triOriginalUnit = 'mg/dL';
_triIsSwapped = false;
_triActiveUnit = 'mg/dL'; _triActiveUnit = 'mg/dL';
notifyListeners(); notifyListeners();
} }
} }

@ -52,14 +52,15 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
final mgdlText = provider.cholMgdlValue ?? ''; final mgdlText = provider.cholMgdlValue ?? '';
final mmolText = provider.cholMmolValue ?? ''; final mmolText = provider.cholMmolValue ?? '';
if (_mgdlController.text != mgdlText) { // Only update if focus is not on this field (to avoid cursor jumping)
if (!_mgdlFocus.hasFocus && _mgdlController.text != mgdlText) {
_mgdlController.text = mgdlText; _mgdlController.text = mgdlText;
_mgdlController.selection = TextSelection.fromPosition( _mgdlController.selection = TextSelection.fromPosition(
TextPosition(offset: _mgdlController.text.length), TextPosition(offset: _mgdlController.text.length),
); );
} }
if (_mmolController.text != mmolText) { if (!_mmolFocus.hasFocus && _mmolController.text != mmolText) {
_mmolController.text = mmolText; _mmolController.text = mmolText;
_mmolController.selection = TextSelection.fromPosition( _mmolController.selection = TextSelection.fromPosition(
TextPosition(offset: _mmolController.text.length), TextPosition(offset: _mmolController.text.length),
@ -81,7 +82,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
focusNode: _mgdlFocus, focusNode: _mgdlFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onBloodCholesterolChanged(value, 'mg/dL'); provider.onCholesterolMgdlChanged(value);
}, },
).paddingOnly(top: 16.h), ).paddingOnly(top: 16.h),
Row( Row(
@ -96,24 +97,21 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
width: 40.h, width: 40.h,
height: 40.h, height: 40.h,
).onPress(() { ).onPress(() {
// Unfocus both fields before switching
_mgdlFocus.unfocus();
_mmolFocus.unfocus();
provider.switchBloodCholesterolValues(); provider.switchBloodCholesterolValues();
if (provider.cholActiveUnit == 'mg/dL') {
_mgdlFocus.requestFocus();
} else {
_mmolFocus.requestFocus();
}
}), }),
], ],
), ),
_buildInputField( _buildInputField(
label: "MMOL/L", label: "MMOL/L",
hint: "6.7", hint: "3.1",
controller: _mmolController, controller: _mmolController,
focusNode: _mmolFocus, focusNode: _mmolFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onCholesterolMmolChanged(value);
provider.onBloodCholesterolChanged(value, 'mmol/L');
}, },
).paddingOnly(bottom: 16.h), ).paddingOnly(bottom: 16.h),
const Divider(height: 1, color: Color(0xFFEEEEEE)), const Divider(height: 1, color: Color(0xFFEEEEEE)),

@ -51,14 +51,16 @@ class _BloodSugarWidgetState extends State<BloodSugarWidget> {
_isProgrammaticChange = true; _isProgrammaticChange = true;
final mgdlText = provider.mgdlValue ?? ''; final mgdlText = provider.mgdlValue ?? '';
final mmolText = provider.mmolValue ?? ''; final mmolText = provider.mmolValue ?? '';
if (_mgdlController.text != mgdlText) {
// Only update if focus is not on this field (to avoid cursor jumping)
if (!_mgdlFocus.hasFocus && _mgdlController.text != mgdlText) {
_mgdlController.text = mgdlText; _mgdlController.text = mgdlText;
_mgdlController.selection = TextSelection.fromPosition( _mgdlController.selection = TextSelection.fromPosition(
TextPosition(offset: _mgdlController.text.length), TextPosition(offset: _mgdlController.text.length),
); );
} }
if (_mmolController.text != mmolText) { if (!_mmolFocus.hasFocus && _mmolController.text != mmolText) {
_mmolController.text = mmolText; _mmolController.text = mmolText;
_mmolController.selection = TextSelection.fromPosition( _mmolController.selection = TextSelection.fromPosition(
TextPosition(offset: _mmolController.text.length), TextPosition(offset: _mmolController.text.length),
@ -84,7 +86,7 @@ class _BloodSugarWidgetState extends State<BloodSugarWidget> {
focusNode: _mgdlFocus, focusNode: _mgdlFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onBloodSugarChanged(value, 'mg/dL'); provider.onBloodSugarMgdlChanged(value);
}, },
).paddingOnly(top: 16.h), ).paddingOnly(top: 16.h),
Row( Row(
@ -99,12 +101,10 @@ class _BloodSugarWidgetState extends State<BloodSugarWidget> {
width: 40.h, width: 40.h,
height: 40.h, height: 40.h,
).onPress(() { ).onPress(() {
// Unfocus both fields before switching
_mgdlFocus.unfocus();
_mmolFocus.unfocus();
provider.switchBloodSugarValues(); provider.switchBloodSugarValues();
if (provider.activeUnit == 'mg/dL') {
_mgdlFocus.requestFocus();
} else {
_mmolFocus.requestFocus();
}
}), }),
], ],
), ),
@ -115,8 +115,7 @@ class _BloodSugarWidgetState extends State<BloodSugarWidget> {
focusNode: _mmolFocus, focusNode: _mmolFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onBloodSugarMmolChanged(value);
provider.onBloodSugarChanged(value, 'mmol/L');
}, },
).paddingOnly(bottom: 16.h), ).paddingOnly(bottom: 16.h),
const Divider(height: 1, color: Color(0xFFEEEEEE)), const Divider(height: 1, color: Color(0xFFEEEEEE)),

@ -53,14 +53,15 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
final mgdlText = provider.triMgdlValue ?? ''; final mgdlText = provider.triMgdlValue ?? '';
final mmolText = provider.triMmolValue ?? ''; final mmolText = provider.triMmolValue ?? '';
if (_mgdlController.text != mgdlText) { // Only update if focus is not on this field (to avoid cursor jumping)
if (!_mgdlFocus.hasFocus && _mgdlController.text != mgdlText) {
_mgdlController.text = mgdlText; _mgdlController.text = mgdlText;
_mgdlController.selection = TextSelection.fromPosition( _mgdlController.selection = TextSelection.fromPosition(
TextPosition(offset: _mgdlController.text.length), TextPosition(offset: _mgdlController.text.length),
); );
} }
if (_mmolController.text != mmolText) { if (!_mmolFocus.hasFocus && _mmolController.text != mmolText) {
_mmolController.text = mmolText; _mmolController.text = mmolText;
_mmolController.selection = TextSelection.fromPosition( _mmolController.selection = TextSelection.fromPosition(
TextPosition(offset: _mmolController.text.length), TextPosition(offset: _mmolController.text.length),
@ -85,7 +86,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
focusNode: _mgdlFocus, focusNode: _mgdlFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onTriglyceridesChanged(value, 'mg/dL'); provider.onTriglyceridesMgdlChanged(value);
}, },
).paddingOnly(top: 16.h), ).paddingOnly(top: 16.h),
@ -101,13 +102,10 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
width: 40.h, width: 40.h,
height: 40.h, height: 40.h,
).onPress(() { ).onPress(() {
// Unfocus both fields before switching
_mgdlFocus.unfocus();
_mmolFocus.unfocus();
provider.switchTriglyceridesValues(); provider.switchTriglyceridesValues();
if (provider.triActiveUnit == 'mg/dL') {
_mgdlFocus.requestFocus();
} else {
_mmolFocus.requestFocus();
}
}), }),
], ],
), ),
@ -119,7 +117,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
focusNode: _mmolFocus, focusNode: _mmolFocus,
onChanged: (value) { onChanged: (value) {
if (_isProgrammaticChange) return; if (_isProgrammaticChange) return;
provider.onTriglyceridesChanged(value, 'mmol/L'); provider.onTriglyceridesMmolChanged(value);
}, },
).paddingOnly(bottom: 16.h), ).paddingOnly(bottom: 16.h),
@ -170,7 +168,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
child: TextField( child: TextField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
onChanged: onChanged, onChanged: onChanged,
@ -185,6 +182,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
style: TextStyle( style: TextStyle(
fontSize: 32.f, fontSize: 32.f,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87,
), ),
), ),
), ),

Loading…
Cancel
Save