@ -44,12 +44,27 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
void initState ( ) {
void initState ( ) {
super . initState ( ) ;
super . initState ( ) ;
dialogService = getIt . get < DialogService > ( ) ;
dialogService = getIt . get < DialogService > ( ) ;
/ / Clear errors when entering the page
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
final viewModel = context . read < HealthTrackersViewModel > ( ) ;
viewModel . clearAllFieldErrors ( ) ;
} ) ;
}
}
@ override
@ override
void dispose ( ) {
void dispose ( ) {
dateController . dispose ( ) ;
dateController . dispose ( ) ;
timeController . dispose ( ) ;
timeController . dispose ( ) ;
/ / Clear errors when leaving the page
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
if ( mounted ) {
final viewModel = context . read < HealthTrackersViewModel > ( ) ;
viewModel . clearAllFieldErrors ( ) ;
}
} ) ;
super . dispose ( ) ;
super . dispose ( ) ;
}
}
@ -94,10 +109,17 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
/ / Save Blood Sugar entry
/ / Save Blood Sugar entry
Future < void > _saveBloodSugarEntry ( HealthTrackersViewModel viewModel ) async {
Future < void > _saveBloodSugarEntry ( HealthTrackersViewModel viewModel ) async {
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Combine date and time
/ / Combine date and time
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
/ / Validate using ViewModel method that sets field errors
if ( ! viewModel . validateBloodSugarEntry ( dateTime ) ) {
/ / Validation failed , errors are already set in viewModel
return ;
}
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Call ViewModel method with callbacks
/ / Call ViewModel method with callbacks
await viewModel . saveBloodSugarEntry (
await viewModel . saveBloodSugarEntry (
dateTime: dateTime ,
dateTime: dateTime ,
@ -115,10 +137,17 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
/ / Save Weight entry
/ / Save Weight entry
Future < void > _saveWeightEntry ( HealthTrackersViewModel viewModel ) async {
Future < void > _saveWeightEntry ( HealthTrackersViewModel viewModel ) async {
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Combine date and time
/ / Combine date and time
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
/ / Validate using ViewModel method that sets field errors
if ( ! viewModel . validateWeightEntry ( dateTime ) ) {
/ / Validation failed , errors are already set in viewModel
return ;
}
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Call ViewModel method with callbacks
/ / Call ViewModel method with callbacks
await viewModel . saveWeightEntry (
await viewModel . saveWeightEntry (
dateTime: dateTime ,
dateTime: dateTime ,
@ -135,10 +164,17 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
/ / Save Blood Pressure entry
/ / Save Blood Pressure entry
Future < void > _saveBloodPressureEntry ( HealthTrackersViewModel viewModel ) async {
Future < void > _saveBloodPressureEntry ( HealthTrackersViewModel viewModel ) async {
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Combine date and time
/ / Combine date and time
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
final dateTime = " ${ dateController . text } ${ timeController . text } " ;
/ / Validate using ViewModel method that sets field errors
if ( ! viewModel . validateBloodPressureEntry ( dateTime ) ) {
/ / Validation failed , errors are already set in viewModel
return ;
}
LoaderBottomSheet . showLoader ( loadingText: LocaleKeys . pleaseWait . tr ( context: context ) ) ;
/ / Call ViewModel method with callbacks
/ / Call ViewModel method with callbacks
await viewModel . saveBloodPressureEntry (
await viewModel . saveBloodPressureEntry (
dateTime: dateTime ,
dateTime: dateTime ,
@ -286,13 +322,19 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
}
}
/ / Reusable method to build text field
/ / Reusable method to build text field
Widget _buildTextField ( TextEditingController controller , String hintText , { TextInputType keyboardType = TextInputType . name } ) {
Widget _buildTextField (
TextEditingController controller ,
String hintText , {
TextInputType keyboardType = TextInputType . name ,
Function ( String ) ? onChanged ,
} ) {
return TextField (
return TextField (
controller: controller ,
controller: controller ,
keyboardType: keyboardType ,
keyboardType: keyboardType ,
maxLines: 1 ,
maxLines: 1 ,
cursorHeight: 14. h ,
cursorHeight: 14. h ,
textAlignVertical: TextAlignVertical . center ,
textAlignVertical: TextAlignVertical . center ,
onChanged: onChanged ,
decoration: InputDecoration (
decoration: InputDecoration (
border: InputBorder . none ,
border: InputBorder . none ,
contentPadding: EdgeInsets . zero ,
contentPadding: EdgeInsets . zero ,
@ -308,6 +350,23 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
) ;
) ;
}
}
/ / Build error text widget
Widget _buildErrorText ( String errorMessage ) {
return Padding (
padding: EdgeInsets . only ( left: 52. w , top: 4. h , bottom: 4. h ) ,
child: Align (
alignment: Alignment . centerLeft ,
child: Text (
errorMessage ,
style: TextStyle (
color: AppColors . primaryRedColor ,
fontSize: 12. f ,
) ,
) ,
) ,
) ;
}
/ / Reusable method to build settings row
/ / Reusable method to build settings row
Widget _buildSettingsRow ( {
Widget _buildSettingsRow ( {
required String icon ,
required String icon ,
@ -401,15 +460,36 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
/ / / Blood Sugar form fields
/ / / Blood Sugar form fields
Widget _buildBloodSugarForm ( HealthTrackersViewModel viewModel ) {
Widget _buildBloodSugarForm ( HealthTrackersViewModel viewModel ) {
return Column (
return Selector < HealthTrackersViewModel , bool > (
selector: ( _ , model ) = > model . hasBloodSugarFormError ,
builder: ( context , hasError , child ) {
return Container (
margin: EdgeInsets . symmetric ( horizontal: 24. w , vertical: 24. h ) ,
padding: EdgeInsets . only ( left: 16. w , right: 16. w , top: 4. h , bottom: 4. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
side: hasError
? BorderSide ( color: AppColors . primaryRedBorderColor , width: 1 )
: null ,
) ,
child: Column (
children: [
children: [
_buildSettingsRow (
_buildSettingsRow (
icon: AppAssets . heightIcon ,
icon: AppAssets . heightIcon ,
label: LocaleKeys . enterBloodSugar . tr ( context: context ) ,
label: LocaleKeys . enterBloodSugar . tr ( context: context ) ,
inputField: _buildTextField ( viewModel . bloodSugarController , ' ' , keyboardType: TextInputType . number ) ,
inputField: _buildTextField (
viewModel . bloodSugarController ,
' ' ,
keyboardType: TextInputType . number ,
onChanged: ( value ) = > viewModel . clearBloodSugarError ( ) ,
) ,
unit: viewModel . selectedBloodSugarUnit ,
unit: viewModel . selectedBloodSugarUnit ,
onUnitTap: ( ) = > _showBloodSugarUnitSelectionBottomSheet ( context , viewModel ) ,
onUnitTap: ( ) = > _showBloodSugarUnitSelectionBottomSheet ( context , viewModel ) ,
) ,
) ,
if ( viewModel . bloodSugarError ! = null )
_buildErrorText ( viewModel . bloodSugarError ! ) ,
_buildDateTimeFields ( ) ,
_buildDateTimeFields ( ) ,
Divider ( height: 1 , color: AppColors . dividerColor ) ,
Divider ( height: 1 , color: AppColors . dividerColor ) ,
_buildSettingsRow (
_buildSettingsRow (
@ -418,56 +498,123 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
value: viewModel . selectedBloodSugarMeasureTime ,
value: viewModel . selectedBloodSugarMeasureTime ,
onRowTap: ( ) = > _showBloodSugarEntryTimeBottomSheet ( context , viewModel ) ,
onRowTap: ( ) = > _showBloodSugarEntryTimeBottomSheet ( context , viewModel ) ,
) ,
) ,
if ( viewModel . bloodSugarMeasureTimeError ! = null )
_buildErrorText ( viewModel . bloodSugarMeasureTimeError ! ) ,
] ,
] ,
) ,
) ;
} ,
) ;
) ;
}
}
/ / / Blood Pressure form fields
/ / / Blood Pressure form fields
Widget _buildBloodPressureForm ( HealthTrackersViewModel viewModel ) {
Widget _buildBloodPressureForm ( HealthTrackersViewModel viewModel ) {
return Column (
return Selector < HealthTrackersViewModel , bool > (
selector: ( _ , model ) = > model . hasBloodPressureFormError ,
builder: ( context , hasError , child ) {
return Container (
margin: EdgeInsets . symmetric ( horizontal: 24. w , vertical: 24. h ) ,
padding: EdgeInsets . only ( left: 16. w , right: 16. w , top: 4. h , bottom: 4. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
side: hasError
? BorderSide ( color: AppColors . primaryRedBorderColor , width: 1 )
: null ,
) ,
child: Column (
children: [
children: [
_buildSettingsRow (
_buildSettingsRow (
icon: AppAssets . bloodPressureIcon ,
icon: AppAssets . bloodPressureIcon ,
iconColor: AppColors . greyTextColor ,
iconColor: AppColors . greyTextColor ,
label: LocaleKeys . enterSystolicValue . tr ( context: context ) ,
label: LocaleKeys . enterSystolicValue . tr ( context: context ) ,
inputField: _buildTextField ( viewModel . systolicController , ' ' , keyboardType: TextInputType . number ) ,
inputField: _buildTextField (
viewModel . systolicController ,
' ' ,
keyboardType: TextInputType . number ,
onChanged: ( value ) = > viewModel . clearSystolicError ( ) ,
) ,
) ,
) ,
if ( viewModel . systolicError ! = null )
_buildErrorText ( viewModel . systolicError ! ) ,
_buildSettingsRow (
_buildSettingsRow (
icon: AppAssets . bloodPressureIcon ,
icon: AppAssets . bloodPressureIcon ,
iconColor: AppColors . greyTextColor ,
iconColor: AppColors . greyTextColor ,
label: LocaleKeys . enterDiastolicValue . tr ( context: context ) ,
label: LocaleKeys . enterDiastolicValue . tr ( context: context ) ,
inputField: _buildTextField ( viewModel . diastolicController , ' ' , keyboardType: TextInputType . number ) ,
inputField: _buildTextField (
viewModel . diastolicController ,
' ' ,
keyboardType: TextInputType . number ,
onChanged: ( value ) = > viewModel . clearDiastolicError ( ) ,
) ,
) ,
) ,
if ( viewModel . diastolicError ! = null )
_buildErrorText ( viewModel . diastolicError ! ) ,
_buildSettingsRow (
_buildSettingsRow (
icon: AppAssets . bodyIcon ,
icon: AppAssets . bodyIcon ,
iconColor: AppColors . greyTextColor ,
iconColor: AppColors . greyTextColor ,
label: LocaleKeys . selectArm . tr ( context: context ) ,
label: LocaleKeys . selectArm . tr ( context: context ) ,
value: viewModel . selectedMeasuredArmDisplay ,
value: viewModel . selectedMeasuredArmDisplay ,
onRowTap: ( ) = > _showMeasuredArmSelectionBottomSheet ( context , viewModel ) ,
onRowTap: ( ) {
_showMeasuredArmSelectionBottomSheet ( context , viewModel ) ;
viewModel . clearMeasuredArmError ( ) ;
} ,
) ,
) ,
if ( viewModel . measuredArmError ! = null )
_buildErrorText ( viewModel . measuredArmError ! ) ,
_buildDateTimeFields ( ) ,
_buildDateTimeFields ( ) ,
] ,
] ,
) ,
) ;
} ,
) ;
) ;
}
}
/ / / Weight form fields
/ / / Weight form fields
Widget _buildWeightForm ( HealthTrackersViewModel viewModel ) {
Widget _buildWeightForm ( HealthTrackersViewModel viewModel ) {
return Column (
return Selector < HealthTrackersViewModel , bool > (
selector: ( _ , model ) = > model . hasWeightFormError ,
builder: ( context , hasError , child ) {
return Container (
margin: EdgeInsets . symmetric ( horizontal: 24. w , vertical: 24. h ) ,
padding: EdgeInsets . only ( left: 16. w , right: 16. w , top: 4. h , bottom: 4. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
side: hasError
? BorderSide ( color: AppColors . primaryRedBorderColor , width: 1 )
: null ,
) ,
child: Column (
children: [
children: [
_buildSettingsRow (
_buildSettingsRow (
icon: AppAssets . weightScale ,
icon: AppAssets . weightScale ,
label: LocaleKeys . enterWeight . tr ( context: context ) ,
label: LocaleKeys . enterWeight . tr ( context: context ) ,
inputField: _buildTextField ( viewModel . weightController , ' ' , keyboardType: TextInputType . number ) ,
inputField: _buildTextField (
viewModel . weightController ,
' ' ,
keyboardType: TextInputType . number ,
onChanged: ( value ) = > viewModel . clearWeightError ( ) ,
) ,
unit: viewModel . selectedWeightUnitDisplay ,
unit: viewModel . selectedWeightUnitDisplay ,
onUnitTap: ( ) = > _showWeightUnitSelectionBottomSheet ( context , viewModel ) ,
onUnitTap: ( ) = > _showWeightUnitSelectionBottomSheet ( context , viewModel ) ,
) ,
) ,
if ( viewModel . weightError ! = null )
_buildErrorText ( viewModel . weightError ! ) ,
_buildDateTimeFields ( ) ,
_buildDateTimeFields ( ) ,
] ,
] ,
) ,
) ;
} ,
) ;
) ;
}
}
/ / / Common date and time fields
/ / / Common date and time fields
Widget _buildDateTimeFields ( ) {
Widget _buildDateTimeFields ( ) {
return Consumer < HealthTrackersViewModel > (
builder: ( context , viewModel , child ) {
return Column (
return Column (
children: [
children: [
SizedBox ( width: 8. w ) ,
SizedBox ( width: 8. w ) ,
@ -495,13 +642,17 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
final parsedDate = DateTime . parse ( val ) ;
final parsedDate = DateTime . parse ( val ) ;
final formattedDate = DateFormat ( ' dd MMM yyyy ' ) . format ( parsedDate ) ;
final formattedDate = DateFormat ( ' dd MMM yyyy ' ) . format ( parsedDate ) ;
dateController . text = formattedDate ;
dateController . text = formattedDate ;
viewModel . clearDateError ( ) ;
log ( " date: $ formattedDate " ) ;
log ( " date: $ formattedDate " ) ;
} catch ( e ) {
} catch ( e ) {
dateController . text = val ;
dateController . text = val ;
viewModel . clearDateError ( ) ;
log ( " date: $ val " ) ;
log ( " date: $ val " ) ;
}
}
} ,
} ,
) ,
) ,
if ( viewModel . dateError ! = null )
_buildErrorText ( viewModel . dateError ! ) ,
Divider ( height: 1 , color: AppColors . dividerColor ) ,
Divider ( height: 1 , color: AppColors . dividerColor ) ,
SizedBox ( width: 8. w ) ,
SizedBox ( width: 8. w ) ,
TextInputWidget (
TextInputWidget (
@ -524,11 +675,16 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
onChange: ( val ) {
onChange: ( val ) {
if ( val = = null ) return ;
if ( val = = null ) return ;
timeController . text = val ;
timeController . text = val ;
viewModel . clearTimeError ( ) ;
log ( " time: $ val " ) ;
log ( " time: $ val " ) ;
} ,
} ,
) ,
) ,
if ( viewModel . timeError ! = null )
_buildErrorText ( viewModel . timeError ! ) ,
] ,
] ,
) ;
) ;
} ,
) ;
}
}
@ override
@ override
@ -555,14 +711,9 @@ class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
) ,
) ,
) ,
) ,
) ,
) ,
child: Container (
margin: EdgeInsets . symmetric ( horizontal: 24. w , vertical: 24. h ) ,
padding: EdgeInsets . only ( left: 16. w , right: 16. w , top: 4. h , bottom: 4. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration ( color: AppColors . whiteColor , borderRadius: 24. r , hasShadow: true ) ,
child: _buildFormFields ( viewModel ) ,
child: _buildFormFields ( viewModel ) ,
) ,
) ,
) ,
) ,
) ,
) ;
) ;
}
}
}
}