@ -1,22 +1,23 @@
import ' dart:developer ' ;
import ' dart:math ' as math ;
import ' package:fl_chart/fl_chart.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:hmg_patient_app_new/core/app_assets.dart ' ;
import ' package:hmg_patient_app_new/core/app_export.dart ' ;
import ' package:hmg_patient_app_new/core/common_models/data_points.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/utils.dart ' ;
import ' package:hmg_patient_app_new/extensions/route_extensions.dart ' ;
import ' package:hmg_patient_app_new/extensions/string_extensions.dart ' ;
import ' package:hmg_patient_app_new/extensions/widget_extensions.dart ' ;
import ' package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart ' ;
import ' package:hmg_patient_app_new/presentation/water_monitor/widgets/bottle_shape_clipper.dart ' ;
import ' package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart ' ;
import ' package:hmg_patient_app_new/presentation/water_monitor/widgets/water_consumption_progress_widget.dart ' ;
import ' package:hmg_patient_app_new/presentation/water_monitor/widgets/hydration_tips_widget.dart ' ;
import ' package:hmg_patient_app_new/presentation/water_monitor/widgets/water_intake_summary_widget.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/chip/app_custom_chip_widget.dart ' ;
import ' package:hmg_patient_app_new/widgets/common_bottom_sheet.dart ' ;
import ' package:hmg_patient_app_new/widgets/graph/custom_graph.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:shimmer/shimmer.dart ' ;
@ -32,360 +33,39 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
void initState ( ) {
super . initState ( ) ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) async {
final vm = context . read < WaterMonitorViewModel > ( ) ;
await vm . initialize ( ) ;
log ( ' WaterMonitor initialized: consumed= ${ vm . totalConsumedMl } , goal= ${ vm . dailyGoalMl } , progress= ${ vm . progressPercent } ' ) ;
await _refreshData ( ) ;
} ) ;
}
Widget _buildHydrationTipsWidget ( ) {
return Container (
margin: EdgeInsets . symmetric ( horizontal: 24. w ) ,
padding: EdgeInsets . all ( 16. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
children: [
Utils . buildSvgWithAssets ( icon: AppAssets . bulb_icon , width: 24. w , height: 24. h ) ,
SizedBox ( width: 8. w ) ,
" Tips to stay hydrated " . needTranslation . toText16 ( isBold: true ) ,
] ,
) ,
SizedBox ( height: 8. h ) ,
" • ${ " Drink before you feel thirsty " } " . needTranslation . toText12 (
fontWeight: FontWeight . w500 ,
color: AppColors . textColorLight ,
) ,
SizedBox ( height: 4. h ) ,
" • ${ " Keep a refillable bottle next to you " } " . needTranslation . toText12 (
fontWeight: FontWeight . w500 ,
color: AppColors . textColorLight ,
) ,
SizedBox ( height: 4. h ) ,
" • ${ " Track your daily intake to stay motivated " } " . needTranslation . toText12 (
fontWeight: FontWeight . w500 ,
color: AppColors . textColorLight ,
) ,
SizedBox ( height: 4. h ) ,
" • ${ " Choose sparkling water instead of soda " } " . needTranslation . toText12 (
fontWeight: FontWeight . w500 ,
color: AppColors . textColorLight ,
) ,
SizedBox ( height: 8. h ) ,
] ,
) ,
) ;
}
Widget _buildWaterIntakeSummaryWidget ( ) {
return Container (
width: double . infinity ,
padding: EdgeInsets . all ( 24. w ) ,
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topLeft ,
end: Alignment . bottomRight ,
colors: [
AppColors . blueGradientColorOne ,
AppColors . blueGradientColorTwo ,
] ,
) ,
) ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Expanded (
flex: isTablet ? 2 : 3 ,
child: Consumer < WaterMonitorViewModel > ( builder: ( context , vm , _ ) {
if ( vm . isLoading ) {
/ / show shimmer placeholder while fetching server - side user details
return _buildLoadingShimmer ( isForHistory: false ) ;
}
final goalMl = vm . dailyGoalMl ;
final consumed = vm . totalConsumedMl ;
final remaining = ( goalMl - consumed ) > 0 ? ( goalMl - consumed ) : 0 ;
final completedPercent = " ${ ( vm . progress * 100 ) . clamp ( 0.0 , 100.0 ) . toStringAsFixed ( 0 ) } % " ;
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
" Next Drink Time " . toText18 ( weight: FontWeight . w600 , color: AppColors . textColor ) ,
vm . nextDrinkTime . toText32 ( weight: FontWeight . w600 , color: AppColors . blueColor ) ,
SizedBox ( height: 12. h ) ,
_buildStatusColumn ( title: " Your Goal " . needTranslation , subTitle: " ${ goalMl } ml " ) ,
SizedBox ( height: 8. h ) ,
_buildStatusColumn ( title: " Remaining " . needTranslation , subTitle: " ${ remaining } ml " ) ,
SizedBox ( height: 8. h ) ,
_buildStatusColumn ( title: " Completed " . needTranslation , subTitle: completedPercent , subTitleColor: AppColors . successColor ) ,
SizedBox ( height: 8. h ) ,
_buildStatusColumn (
title: " Hydration Status " . needTranslation ,
subTitle: vm . hydrationStatus ,
subTitleColor: vm . hydrationStatusColor ,
) ,
] ,
) ;
} ) ,
) ,
SizedBox ( width: isTablet ? 32 : 16. w ) ,
Expanded (
flex: isTablet ? 1 : 2 ,
child: _buildWaterBottleWidget ( ) ,
) ,
] ,
) ,
_buildBottomActionWidgets ( ) ,
] ,
) ,
) ;
}
Widget _buildStatusColumn ( { required String title , required String subTitle , Color ? subTitleColor } ) {
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
" $ title : " . toText16 ( weight: FontWeight . w500 , color: AppColors . textColor ) ,
subTitle . toText12 (
fontWeight: FontWeight . w600 ,
color: subTitleColor ? ? AppColors . greyTextColor ,
) ,
] ,
) ;
/ / / Refresh data by calling initialize on the view model
Future < void > _refreshData ( ) async {
final vm = context . read < WaterMonitorViewModel > ( ) ;
await vm . initialize ( ) ;
}
Widget _buildWaterBottleWidget ( ) {
return Consumer < WaterMonitorViewModel > (
builder: ( context , vm , _ ) {
final progressPercent = ( vm . progress * 100 ) . clamp ( 0.0 , 100.0 ) ;
log ( " progressPercent: $ progressPercent " ) ;
/ / SVG aspect ratio
const svgAspectRatio = 315.0 / 143.0 ; / / ~ 2.2
/ / Responsive bottle sizing with device - specific constraints
double bottleWidth ;
if ( isTablet ) {
/ / Tablet: use logical pixels ( not scaled ) with a reasonable max
bottleWidth = math . min ( SizeUtils . width * 0.15 , 180.0 ) ; / / 15 % of width , max 180
} else if ( isFoldable ) {
/ / Foldable: moderate scaling
bottleWidth = math . min ( 100. w , 160.0 ) ;
} else {
/ / Phone: use your responsive . w with a cap
bottleWidth = math . min ( 120. w , 140.0 ) ;
}
final bottleHeight = bottleWidth * svgAspectRatio ;
/ / Fillable area percentages ( calibrated to your SVG )
final fillableHeightPercent = 0.7 ;
const fillableWidthPercent = 0.8 ;
final fillableHeight = bottleHeight * fillableHeightPercent ;
final fillableWidth = bottleWidth * fillableWidthPercent ;
/ / Device - specific positioning offsets
final double leftOffset = isTablet ? 4. w : 8. w ;
final double bottomOffset = isTablet ? - 65. h : - 78. h ;
return SizedBox (
height: bottleHeight ,
width: bottleWidth ,
child: Stack (
fit: StackFit . expand ,
alignment: Alignment . center ,
children: [
/ / Bottle SVG outline
Center (
child: Utils . buildSvgWithAssets (
icon: AppAssets . waterBottle ,
height: bottleHeight ,
width: bottleWidth ,
fit: BoxFit . contain ,
) ,
) ,
/ / Wave and bubbles clipped to bottle shape
Positioned . fill (
left: leftOffset ,
bottom: bottomOffset ,
child: Center (
child: SizedBox (
width: fillableWidth ,
height: fillableHeight ,
child: ClipPath (
clipper: BottleShapeClipper ( ) ,
child: Stack (
alignment: Alignment . bottomCenter ,
children: [
/ / Animated wave
Positioned (
child: WaterConsumptionProgressWidget (
progress: progressPercent ,
size: math . min ( fillableWidth , fillableHeight ) ,
containerWidth: fillableWidth ,
containerHeight: fillableHeight ,
waveDuration: const Duration ( milliseconds: 3000 ) ,
waveColor: AppColors . blueColor ,
) ,
) ,
/ / Bubbles ( only show if progress > 10 % )
if ( progressPercent > 10 )
Positioned (
bottom: fillableHeight * 0.12 ,
child: Utils . buildSvgWithAssets (
icon: AppAssets . waterBottleOuterBubbles ,
/ / Cap bubble size based on device type
height: isTablet ? math . min ( 45.0 , fillableHeight * 0.2 ) : math . min ( 55.0 , fillableHeight * 0.22 ) ,
width: fillableWidth * 0.65 ,
) ,
) ,
] ,
) ,
) ,
) ,
) ,
) ,
] ,
Widget _buildLoadingShimmer ( { bool isForHistory = true } ) {
return ListView . separated (
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
padding: EdgeInsets . all ( 0. w ) ,
itemCount: 4 ,
separatorBuilder: ( _ , __ ) = > SizedBox ( height: 12. h ) ,
itemBuilder: ( context , index ) {
return Shimmer . fromColors (
baseColor: AppColors . shimmerBaseColor ,
highlightColor: AppColors . shimmerHighlightColor ,
child: Container (
height: isForHistory ? 60. h : 40. h ,
decoration: BoxDecoration (
color: AppColors . whiteColor ,
borderRadius: BorderRadius . circular ( 10. r ) ,
) ,
) ,
) ;
} ,
) ;
}
_buildBottomActionWidgets ( ) {
return Consumer < WaterMonitorViewModel > ( builder: ( context , vm , _ ) {
final cupAmount = vm . selectedCupCapacityMl ;
return Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
InkWell (
onTap: ( ) async {
/ / Undo last activity via API
if ( cupAmount > 0 ) {
await vm . undoUserActivity ( ) ;
}
} ,
child: Utils . buildSvgWithAssets (
icon: AppAssets . minimizeIcon ,
height: 20. w ,
width: 20. w ,
iconColor: AppColors . textColor ,
) ,
) ,
Container (
margin: EdgeInsets . symmetric ( horizontal: 4. w ) ,
padding: EdgeInsets . symmetric ( horizontal: 8. w , vertical: 4. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . blueColor ,
borderRadius: 99. r ,
hasShadow: true ,
) ,
child: ( cupAmount > 0 ? " + $ cupAmount ml " : " + 0ml " ) . toText12 (
fontWeight: FontWeight . w600 ,
color: AppColors . whiteColor ,
) ,
) ,
InkWell (
onTap: ( ) async {
/ / Insert user activity via API
if ( cupAmount > 0 ) {
await vm . insertUserActivity ( quantityIntake: cupAmount ) ;
}
} ,
child: Utils . buildSvgWithAssets (
icon: AppAssets . addIconDark ,
) ,
) ,
] ,
) ,
SizedBox ( height: 8. h ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
_buildActionButton (
onTap: ( ) = > showSwitchCupBottomSheet ( context ) ,
overlayWidget: AppAssets . refreshIcon ,
title: " Switch Cup " . needTranslation ,
icon: Utils . buildSvgWithAssets ( icon: AppAssets . glassIcon , height: 24. w , width: 24. w ) ,
) ,
_buildActionButton (
onTap: ( ) { } ,
title: " Add Water " . needTranslation ,
icon: Utils . buildSvgWithAssets ( icon: AppAssets . glassIcon , height: 24. w , width: 24. w ) ,
) ,
_buildActionButton (
onTap: ( ) = > context . navigateWithName ( AppRoutes . waterMonitorSettingsScreen ) ,
title: " Settings " . needTranslation ,
icon: Icon (
Icons . settings ,
color: AppColors . blueColor ,
size: 24. w ,
) ,
) ,
] ,
) ,
] ,
) ;
} ) ;
}
_buildActionButton ( { String ? overlayWidget , required String title , required Widget icon , required VoidCallback onTap } ) {
return InkWell (
onTap: onTap ,
child: Column (
children: [
Stack (
children: [
Container (
height: 46. w ,
width: 46. w ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . blueColor . withValues ( alpha: 0.14 ) ,
borderRadius: 12. r ,
hasShadow: true ,
) ,
child: Center ( child: icon ) ,
) ,
if ( overlayWidget ! = null ) . . . [
Positioned (
top: 0 ,
right: 0 ,
child: Container (
padding: EdgeInsets . all ( 2. w ) ,
height: 16. w ,
width: 16. w ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . blueColor ,
borderRadius: 100. r ,
hasShadow: true ,
) ,
child: Center ( child: Utils . buildSvgWithAssets ( icon: AppAssets . refreshIcon , iconColor: AppColors . whiteColor ) ) ,
) ,
) ,
]
] ,
) ,
SizedBox ( height: 4. h ) ,
title . toText10 ( ) ,
] ,
) ,
) ;
}
Widget buildHistoryListTile ( { required String title , required String subTitle } ) {
return Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
@ -452,44 +132,14 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
) ,
] ,
) ,
if ( ! viewModel . isGraphView )
_buildHistoryListView ( viewModel )
else
Center (
child: Padding (
padding: EdgeInsets . symmetric ( vertical: 32. h ) ,
child: " Graph view coming soon " . toText14 ( color: AppColors . greyTextColor ) ,
) ,
) ,
SizedBox ( height: 12. h ) ,
if ( ! viewModel . isGraphView ) _buildHistoryListView ( viewModel ) else _buildHistoryFlowchart ( )
] ,
) ;
} ) ,
) ;
}
Widget _buildLoadingShimmer ( { bool isForHistory = true } ) {
return ListView . separated (
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
padding: EdgeInsets . all ( 0. w ) ,
itemCount: 4 ,
separatorBuilder: ( _ , __ ) = > SizedBox ( height: 12. h ) ,
itemBuilder: ( context , index ) {
return Shimmer . fromColors (
baseColor: Colors . grey [ 300 ] ! ,
highlightColor: Colors . grey [ 100 ] ! ,
child: Container (
height: isForHistory ? 60. h : 40. h ,
decoration: BoxDecoration (
color: Colors . white ,
borderRadius: BorderRadius . circular ( 10. r ) ,
) ,
) ,
) ;
} ,
) ;
}
Widget _buildHistoryListView ( WaterMonitorViewModel viewModel ) {
final selectedDuration = viewModel . selectedDurationFilter ;
@ -524,14 +174,19 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
}
} else if ( selectedDuration = = ' Weekly ' ) {
if ( viewModel . weekProgressList . isNotEmpty ) {
for ( var dayData in viewModel . weekProgressList ) {
/ / Show today + last 6 days ( total 7 days )
final totalDays = viewModel . weekProgressList . length ;
final startIndex = totalDays > 7 ? totalDays - 7 : 0 ;
final weekDataToShow = viewModel . weekProgressList . skip ( startIndex ) . toList ( ) ;
for ( var dayData in weekDataToShow ) {
listItems . add (
buildHistoryListTile (
title: dayData . dayName ? ? ' Unknown ' ,
subTitle: " ${ dayData . percentageConsumed ? . toStringAsFixed ( 1 ) ? ? ' 0 ' } % " ,
) ,
) ;
if ( dayData ! = viewModel. weekProgressList . last ) {
if ( dayData ! = weekDataToShow . last ) {
listItems . add ( Divider ( height: 1 , color: AppColors . dividerColor ) ) ;
}
}
@ -547,14 +202,20 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
}
} else if ( selectedDuration = = ' Monthly ' ) {
if ( viewModel . monthProgressList . isNotEmpty ) {
for ( var monthData in viewModel . monthProgressList ) {
/ / Show current month + last 6 months ( total 7 months )
/ / Reverse order to show oldest to newest ( top to bottom )
final totalMonths = viewModel . monthProgressList . length ;
final startIndex = totalMonths > 7 ? totalMonths - 7 : 0 ;
final monthDataToShow = viewModel . monthProgressList . skip ( startIndex ) . toList ( ) . reversed . toList ( ) ;
for ( var monthData in monthDataToShow ) {
listItems . add (
buildHistoryListTile (
title: monthData . monthName ? ? ' Unknown ' ,
subTitle: " ${ monthData . percentageConsumed ? . toStringAsFixed ( 1 ) ? ? ' 0 ' } % " ,
) ,
) ;
if ( monthData ! = viewModel. monthProgressList . last ) {
if ( monthData ! = monthDataToShow . last ) {
listItems . add ( Divider ( height: 1 , color: AppColors . dividerColor ) ) ;
}
}
@ -572,10 +233,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
/ / Return scrollable list with min and max height constraints
return ConstrainedBox (
constraints: BoxConstraints (
minHeight: 80. h ,
maxHeight: 270. h ,
) ,
constraints: BoxConstraints ( minHeight: 80. h , maxHeight: 270. h ) ,
child: viewModel . isLoading
? _buildLoadingShimmer ( ) . paddingOnly ( top: 16. h )
: listItems . isEmpty
@ -592,6 +250,197 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
) ;
}
Widget _buildHistoryFlowchart ( ) {
return Consumer < WaterMonitorViewModel > (
builder: ( context , viewModel , _ ) {
final selectedDuration = viewModel . selectedDurationFilter ;
/ / Build dynamic data points based on selected duration
List < DataPoint > dataPoints = [ ] ;
if ( selectedDuration = = ' Daily ' ) {
/ / For daily , we show a single bar / point with today ' s percentage
if ( viewModel . todayProgressList . isNotEmpty ) {
final todayData = viewModel . todayProgressList . first ;
final percentage = todayData . percentageConsumed ? . toDouble ( ) ? ? 0.0 ;
dataPoints . add (
DataPoint (
value: percentage ,
actualValue: percentage . toStringAsFixed ( 1 ) ,
label: ' Today ' ,
displayTime: ' Today ' ,
unitOfMeasurement: ' % ' ,
time: DateTime . now ( ) ,
) ,
) ;
}
} else if ( selectedDuration = = ' Weekly ' ) {
/ / For weekly , show today + last 6 days ( total 7 days )
if ( viewModel . weekProgressList . isNotEmpty ) {
final totalDays = viewModel . weekProgressList . length ;
final startIndex = totalDays > 7 ? totalDays - 7 : 0 ;
final weekDataToShow = viewModel . weekProgressList . skip ( startIndex ) . toList ( ) ;
for ( var dayData in weekDataToShow ) {
final percentage = dayData . percentageConsumed ? . toDouble ( ) ? ? 0.0 ;
final dayName = dayData . dayName ? ? ' Day ${ dayData . dayNumber } ' ;
dataPoints . add (
DataPoint (
value: percentage ,
actualValue: percentage . toStringAsFixed ( 1 ) ,
label: DateUtil . getShortWeekDayName ( dayName ) ,
displayTime: dayName ,
unitOfMeasurement: ' % ' ,
time: DateTime . now ( ) ,
) ,
) ;
}
}
} else if ( selectedDuration = = ' Monthly ' ) {
/ / For monthly , show current month + last 6 months ( total 7 months )
/ / Reverse order to show oldest to newest ( left to right )
if ( viewModel . monthProgressList . isNotEmpty ) {
final totalMonths = viewModel . monthProgressList . length ;
final startIndex = totalMonths > 7 ? totalMonths - 7 : 0 ;
final monthDataToShow = viewModel . monthProgressList . skip ( startIndex ) . toList ( ) . reversed . toList ( ) ;
for ( var monthData in monthDataToShow ) {
final percentage = monthData . percentageConsumed ? . toDouble ( ) ? ? 0.0 ;
final monthName = monthData . monthName ? ? ' Month ${ monthData . monthNumber } ' ;
dataPoints . add (
DataPoint (
value: percentage ,
actualValue: percentage . toStringAsFixed ( 1 ) ,
label: DateUtil . getShortMonthName ( monthName ) ,
displayTime: monthName ,
unitOfMeasurement: ' % ' ,
time: DateTime . now ( ) ,
) ,
) ;
}
}
}
/ / If no data , show empty state
if ( dataPoints . isEmpty ) {
return Container (
padding: EdgeInsets . symmetric ( vertical: 80. h ) ,
child: Center (
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Icon ( Icons . bar_chart , size: 48. w , color: AppColors . greyTextColor . withValues ( alpha: 0.5 ) ) ,
SizedBox ( height: 12. h ) ,
" No chart data available " . toText14 ( color: AppColors . greyTextColor ) ,
] ,
) ,
) ,
) ;
}
/ / Show loading shimmer while fetching data
if ( viewModel . isLoading ) {
return Container (
padding: EdgeInsets . symmetric ( vertical: 40. h ) ,
child: _buildLoadingShimmer ( ) ,
) ;
}
/ / Define ranges for percentage ( 0 - 100 % )
const double low = 25.0 ; / / Below 25 % is low
const double medium = 50.0 ; / / 25 - 50 % is medium
const double good = 75.0 ; / / 50 - 75 % is good
const double maxY = 100.0 ; / / Max is 100 %
return CustomGraph (
dataPoints: dataPoints ,
makeGraphBasedOnActualValue: true ,
leftLabelReservedSize: 50. w ,
leftLabelInterval: 25 ,
showGridLines: true ,
maxY: maxY ,
minY: 0 ,
maxX: dataPoints . length > 1 ? dataPoints . length . toDouble ( ) - 0.75 : 1.0 ,
minX: - 0.2 ,
horizontalInterval: 25 ,
/ / Grid lines every 25 %
showShadow: true ,
getDrawingHorizontalLine: ( value ) {
/ / Draw dashed lines at 25 % , 50 % , 75 %
if ( value = = low | | value = = medium | | value = = good ) {
return FlLine (
color: AppColors . greyTextColor . withValues ( alpha: 0.3 ) ,
strokeWidth: 1.5 ,
dashArray: [ 8 , 4 ] ,
) ;
}
return FlLine ( color: AppColors . transparent , strokeWidth: 0 ) ;
} ,
leftLabelFormatter: ( value ) {
/ / Show percentage labels at key points
if ( value = = 0 ) return ' 0% ' . toText10 ( weight: FontWeight . w600 ) ;
if ( value = = 25 ) return ' 25% ' . toText10 ( weight: FontWeight . w600 ) ;
if ( value = = 50 ) return ' 50% ' . toText10 ( weight: FontWeight . w600 ) ;
if ( value = = 75 ) return ' 75% ' . toText10 ( weight: FontWeight . w600 ) ;
if ( value = = 100 ) return ' 100% ' . toText10 ( weight: FontWeight . w600 ) ;
return SizedBox . shrink ( ) ;
} ,
graphColor: AppColors . successColor ,
graphShadowColor: AppColors . successColor . withValues ( alpha: 0.15 ) ,
bottomLabelFormatter: ( value , data ) {
if ( data . isEmpty ) return SizedBox . shrink ( ) ;
/ / Only show labels for whole number positions ( not fractional )
if ( ( value - value . round ( ) ) . abs ( ) > 0.01 ) {
return SizedBox . shrink ( ) ;
}
int index = value . round ( ) ;
if ( index < 0 | | index > = data . length ) return SizedBox . shrink ( ) ;
/ / For daily , show only index 0
if ( selectedDuration = = ' Daily ' & & index = = 0 ) {
return Padding (
padding: EdgeInsets . only ( top: 8. h ) ,
child: data [ index ] . label . toText10 (
weight: FontWeight . w600 ,
color: AppColors . labelTextColor ,
) ,
) ;
}
/ / For weekly , show all 7 days ( today + last 6 days )
if ( selectedDuration = = ' Weekly ' & & index < 7 ) {
return Padding (
padding: EdgeInsets . only ( top: 8. h ) ,
child: data [ index ] . label . toText10 (
weight: FontWeight . w600 ,
color: AppColors . labelTextColor ,
) ,
) ;
}
/ / For monthly , show all 7 months ( current month + last 6 months )
if ( selectedDuration = = ' Monthly ' & & index < 7 ) {
return Padding (
padding: EdgeInsets . only ( top: 8. h ) ,
child: data [ index ] . label . toText10 (
weight: FontWeight . w600 ,
color: AppColors . labelTextColor ,
) ,
) ;
}
return SizedBox . shrink ( ) ;
} ,
scrollDirection: selectedDuration = = ' Monthly ' ? Axis . horizontal : Axis . vertical ,
height: 250. h ,
spotColor: AppColors . successColor ,
) ;
} ,
) ;
}
/ / Reusable method to build selection row widget
Widget _buildSelectionRow ( {
required String value ,
@ -634,7 +483,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
message: " " ,
child: Container (
padding: EdgeInsets . only ( left: 16. w , right: 16. w , top: 4. h , bottom: 4. h ) ,
decoration: BoxDecoration ( color: Colors. white , borderRadius: BorderRadius . circular ( 20. r ) ) ,
decoration: BoxDecoration ( color: App Colors. white Color , borderRadius: BorderRadius . circular ( 20. r ) ) ,
child: ListView . separated (
shrinkWrap: true ,
itemCount: items . length ,
@ -667,43 +516,162 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
) ;
}
/ / / Handle reminder button tap ( Set or Cancel )
Future < void > _handleReminderButtonTap ( WaterMonitorViewModel viewModel ) async {
if ( viewModel . isWaterReminderEnabled ) {
/ / Cancel reminders
_showCancelReminderConfirmation ( viewModel ) ;
} else {
/ / Set reminders
await _setReminders ( viewModel ) ;
}
}
/ / / Show confirmation bottom sheet before cancelling reminders
void _showCancelReminderConfirmation ( WaterMonitorViewModel viewModel ) {
showCommonBottomSheetWithoutHeight (
title: ' Notice ' . needTranslation ,
context ,
child: Utils . getWarningWidget (
loadingText: " Are you sure you want to cancel all water reminders? " . needTranslation ,
isShowActionButtons: true ,
onCancelTap: ( ) {
Navigator . pop ( context ) ;
} ,
onConfirmTap: ( ) async {
Navigator . pop ( context ) ;
await _cancelReminders ( viewModel ) ;
} ,
) ,
callBackFunc: ( ) { } ,
isFullScreen: false ,
isCloseButtonVisible: true ,
isDismissible: true ,
) ;
}
/ / / Set water reminders
Future < void > _setReminders ( WaterMonitorViewModel viewModel ) async {
/ / Schedule reminders
final success = await viewModel . scheduleWaterReminders ( ) ;
if ( success ) {
final times = await viewModel . getScheduledReminderTimes ( ) ;
_showReminderScheduledDialog ( times ) ;
}
}
/ / / Cancel water reminders
Future < void > _cancelReminders ( WaterMonitorViewModel viewModel ) async {
final success = await viewModel . cancelWaterReminders ( ) ;
}
/ / / Show bottom sheet with scheduled reminder times
void _showReminderScheduledDialog ( List < DateTime > times ) {
showCommonBottomSheetWithoutHeight (
title: ' Reminders Set! ' . needTranslation ,
context ,
isCloseButtonVisible: false ,
isDismissible: false ,
child: Padding (
padding: EdgeInsets . only ( top: 16. w , left: 16. w , right: 16. w ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Utils . getSuccessWidget ( loadingText: ' Daily water reminders scheduled at: ' . needTranslation ) ,
SizedBox ( height: 16. h ) ,
Wrap (
spacing: 8. w ,
runSpacing: 8. h ,
children: times
. map (
( time ) = > AppCustomChipWidget (
icon: AppAssets . bell ,
iconColor: AppColors . quickLoginColor ,
richText: _formatTime ( time ) . toText14 ( ) ,
padding: EdgeInsets . symmetric ( horizontal: 8. w , vertical: 8. h ) ,
) ,
)
. toList ( ) ,
) ,
SizedBox ( height: 24. h ) ,
/ / OK button
Row (
children: [
Expanded (
child: CustomButton (
height: 56. h ,
text: ' OK ' . needTranslation ,
onPressed: ( ) = > Navigator . of ( context ) . pop ( ) ,
textColor: AppColors . whiteColor ,
) ,
) ,
] ,
) ,
] ,
) ,
) ,
callBackFunc: ( ) { } ,
) ;
}
/ / / Format DateTime to readable time string
String _formatTime ( DateTime time ) {
final hour = time . hour ;
final minute = time . minute ;
final hour12 = hour > 12 ? hour - 12 : ( hour = = 0 ? 12 : hour ) ;
final period = hour > = 12 ? ' PM ' : ' AM ' ;
return ' ${ hour12 . toString ( ) . padLeft ( 2 , ' 0 ' ) } : ${ minute . toString ( ) . padLeft ( 2 , ' 0 ' ) } $ period ' ;
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
backgroundColor: AppColors . bgScaffoldColor ,
body: CollapsingListView (
title: " Water Consumption " . needTranslation ,
bottomChild: Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
) ,
child: Padding (
padding: EdgeInsets . all ( 24. w ) ,
child: CustomButton (
text: " Set Reminder " . needTranslation ,
textColor: AppColors . successColor ,
backgroundColor: AppColors . successLightBgColor ,
onPressed: ( ) { } ,
icon: AppAssets . bell ,
iconColor: AppColors . successColor ,
borderRadius: 12. r ,
borderColor: AppColors . transparent ,
padding: EdgeInsets . symmetric ( vertical: 14. h ) ,
) ,
) ,
bottomChild: Consumer < WaterMonitorViewModel > (
builder: ( context , viewModel , child ) {
return Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: true ,
) ,
child: Padding (
padding: EdgeInsets . all ( 24. w ) ,
child: CustomButton (
text: viewModel . isWaterReminderEnabled ? " Cancel Reminders " . needTranslation : " Set Reminder " . needTranslation ,
textColor: viewModel . isWaterReminderEnabled ? AppColors . errorColor : AppColors . successColor ,
backgroundColor: viewModel . isWaterReminderEnabled ? AppColors . errorColor . withValues ( alpha: 0.1 ) : AppColors . successLightBgColor ,
onPressed: ( ) = > _handleReminderButtonTap ( viewModel ) ,
icon: viewModel . isWaterReminderEnabled ? null : AppAssets . bell ,
iconColor: viewModel . isWaterReminderEnabled ? AppColors . errorColor : AppColors . successColor ,
borderRadius: 12. r ,
borderColor: AppColors . transparent ,
padding: EdgeInsets . symmetric ( vertical: 14. h ) ,
) ,
) ,
) ;
} ,
) ,
child: Column (
children: [
SizedBox ( height: 16. h ) ,
_buildWaterIntakeSummaryWidget ( ) ,
SizedBox ( height: 16. h ) ,
_buildHistoryGraphOrList ( ) ,
SizedBox ( height: 16. h ) ,
_buildHydrationTipsWidget ( ) ,
SizedBox ( height: 16. h ) ,
] ,
child: RefreshIndicator (
onRefresh: _refreshData ,
color: AppColors . blueColor ,
backgroundColor: AppColors . whiteColor ,
child: Column (
children: [
SizedBox ( height: 16. h ) ,
const WaterIntakeSummaryWidget ( ) ,
SizedBox ( height: 16. h ) ,
_buildHistoryGraphOrList ( ) ,
SizedBox ( height: 16. h ) ,
const HydrationTipsWidget ( ) ,
SizedBox ( height: 16. h ) ,
] ,
) ,
) ,
) ,
) ;